Cocos2d screenShot

Forums Programming cocos2d 3rd party extensions Cocos2d screenShot

This topic contains 124 replies, has 74 voices, and was last updated by  sefiroths 7 months, 3 weeks ago.

Viewing 25 posts - 1 through 25 (of 125 total)
Author Posts
Author Posts
September 7, 2009 at 12:30 am #217186

manucorporat
Participant
@manucorporat

Cocos2D should have a function for capture the OpenGL image in screen.

I developed a function for this.

- (Texture2D*) cocos2DScreenShot {
CGSize size = [self displaySize];

//Create un buffer for pixels
int bufferLenght=size.width*size.height*4;
unsigned char buffer[bufferLenght];

//Read Pixels from OpenGL
glReadPixels(0,0,size.width,size.height,GL_RGBA,GL_UNSIGNED_BYTE,&buffer);
CGDataProviderRef ref = CGDataProviderCreateWithData(NULL, &buffer, bufferLenght, NULL);
CGImageRef iref = CGImageCreate(size.width,size.height,8,32,size.width*4,CGColorSpaceCreateDeviceRGB(),kCGBitmapByteOrderDefault,ref,NULL,true,kCGRenderingIntentDefault);

uint32_t *pixels = (uint32_t *)malloc(bufferLenght);
CGContextRef context = CGBitmapContextCreate(pixels, [self winSize].width, [self winSize].height, 8, [self winSize].width*4, CGImageGetColorSpace(iref), kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);

CGContextTranslateCTM(context,0, size.height);
CGContextScaleCTM(context, 1.0, -1.0);

switch (deviceOrientation_) {
case CCDeviceOrientationPortrait:
break;
case CCDeviceOrientationPortraitUpsideDown:
CGContextRotateCTM(context, CC_DEGREES_TO_RADIANS(180));
CGContextTranslateCTM(context,-size.width, -size.height);
break;
case CCDeviceOrientationLandscapeLeft:
CGContextRotateCTM(context, CC_DEGREES_TO_RADIANS(-90));
CGContextTranslateCTM(context,-size.height, 0);
break;
case CCDeviceOrientationLandscapeRight:
CGContextRotateCTM(context, CC_DEGREES_TO_RADIANS(90));
CGContextTranslateCTM(context,size.width*0.5, -size.height);
break;
}
CGContextDrawImage(context, CGRectMake(0.0, 0.0, size.width, size.height), iref);
UIImage *outputImage = [UIImage imageWithCGImage:CGBitmapContextCreateImage(context)];
Texture2D *texture = [[Texture2D alloc] initWithImage:outputImage];

//Dealloc
CGDataProviderRelease(ref);
CGImageRelease(iref);
CGContextRelease(context);
free(pixels);

return texture;
}

I found the original code in: http://stackoverflow.com/questions/962390/capturing-eaglview-content-with-alpha-channel-on-iphone

but I edited it because didn’t support orientation and it didn’t release the memory.

I open a inssue in the cocos2d-iphone Project:

http://code.google.com/p/cocos2d-iphone/issues/detail?id=533

September 7, 2009 at 12:36 am #259610

Mitch Lindgren
@lindgrenm

While this does sound like a potentially useful feature, are you aware that you can capture screenshots on the iPhone by click the home and lock buttons simultaneously? Or, on the simulator, you can use OSX app “Grab” to get screenshots. So if you don’t need some kind of automatic function for taking screenshots, those are potential options.

September 7, 2009 at 12:54 am #259611

manucorporat
Participant
@manucorporat

yes, I know how I can capture a screenshot manualy. I use this method for public screenshots.

But my function capture only the OpenGl context.

And for example I use this function for capture the “Best Moments in the Game” and show them when I finished the match.

Do you understand me?

September 7, 2009 at 3:51 am #259612

mobilebros
Moderator
@mobilebros

Cool! This actually would be a nice feature for exactly the purpose you just described.

September 7, 2009 at 11:02 am #259613

manucorporat
Participant
@manucorporat
- (void) endLoad{

...
...
...
[self scheduler:@selector(screenshot) interval:3];
textureStore = [NSMutableArray arrayWithCapacity:10];
}

- (void) screenshot{
[textureStore addObject:Director sharedDirector] cocos2DScreenShot;
}

- (void) gameover{
int i=0;
for(Texture2D *texture in textureStore){
Sprite *screen = [Sprite spriteWithTexture:Director sharedDirector] cocos2DScreenShot;
[screen setScale:0.4];
[screen setAnchorPoint:ccp(0,0)];
[screen setPosition:ccp([texture contentSize].width*i,0)];
[self addChild:screen];
i++;
}
}

September 14, 2009 at 4:50 pm #259614

manucorporat
Participant
@manucorporat

I have updated the code:

-It is easier to configure.

- (UIImage*) screenShotUIImage; //return a UIImage
- (Texture2D*) screenShotTexture2D; //return a Texture2D

This is the final code:

- (UIImage*) screenShotUIImage {
CGSize size = [self displaySize];
//Create un buffer for pixels
GLuint bufferLenght=size.width*size.height*4;
GLubyte *buffer = (GLubyte *) malloc(bufferLenght);

//Read Pixels from OpenGL
glReadPixels(0,0,size.width,size.height,GL_RGBA,GL_UNSIGNED_BYTE,buffer);
//Make data provider with data.
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, buffer, bufferLenght, NULL);

//Configure image
int bitsPerComponent = 8;
int bitsPerPixel = 32;
int bytesPerRow = 4 * size.width;
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
CGImageRef iref = CGImageCreate(size.width,size.height,bitsPerComponent,bitsPerPixel,bytesPerRow,colorSpaceRef,bitmapInfo,provider,NULL,NO,renderingIntent);

uint32_t *pixels = (uint32_t *)malloc(bufferLenght);
CGContextRef context = CGBitmapContextCreate(pixels, [self winSize].width, [self winSize].height, 8, [self winSize].width*4, CGImageGetColorSpace(iref), kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);

CGContextTranslateCTM(context,0, size.height);
CGContextScaleCTM(context, 1.0, -1.0);

switch (deviceOrientation_) {
case CCDeviceOrientationPortrait:
break;
case CCDeviceOrientationPortraitUpsideDown:
CGContextRotateCTM(context, CC_DEGREES_TO_RADIANS(180));
CGContextTranslateCTM(context,-size.width, -size.height);
break;
case CCDeviceOrientationLandscapeLeft:
CGContextRotateCTM(context, CC_DEGREES_TO_RADIANS(-90));
CGContextTranslateCTM(context,-size.height, 0);
break;
case CCDeviceOrientationLandscapeRight:
CGContextRotateCTM(context, CC_DEGREES_TO_RADIANS(90));
CGContextTranslateCTM(context,size.width*0.5, -size.height);
break;
}
CGContextDrawImage(context, CGRectMake(0.0, 0.0, size.width, size.height), iref);
UIImage *outputImage = [UIImage imageWithCGImage:CGBitmapContextCreateImage(context)];

//Dealloc
CGDataProviderRelease(provider);
CGImageRelease(iref);
CGContextRelease(context);
free(buffer);
free(pixels);

return outputImage;
}

- (Texture2D*) screenShotTexture2D {
return Texture2D alloc] initWithImage:[self screenShotUIImage;
}

September 14, 2009 at 11:16 pm #259615

quano
@quano

Nice effort. Might come in handy.

November 6, 2009 at 2:29 pm #259616

zombie
Participant
@zombie

For some reason glReadPixels returns all black when screenShotUIImage is called. However, calling screenShotUIImage from another place in code, it works. Any ideas? Could it be that the buffer is not ready or is there something else I need to look at. A glGetError after glReadPixels returned zero.

November 6, 2009 at 4:01 pm #259617

manucorporat
Participant
@manucorporat

It is strange, Where did you call screenShotUIImage? You can not call this method in other thread.

November 6, 2009 at 6:51 pm #259618

zombie
Participant
@zombie

Its single threaded. The one time screenShotUIImage is called from a Layer. Works. The other time from the main scene directly. Does not work. Code is the same.

November 19, 2009 at 8:57 pm #259619

nsxdavid
@nsxdavid

The reason for the black shots from note I added to the issue:

Interesting problem with this approach…

Because it uses glReadPixels() it will always give a blank image if used in any

scheduled selector callback. This is because the mainloop clears the buffer then

does the scheduled selectors.

Swapping the order of the mainLoop so that it does the glClear() after the scheduler

works, but it guarantees that any drawing done in scheduled will no longer work.

Which seems reasonable, but perhaps a breaking change.

Another idea might be some sort of preclear callback, or post-bufferswap callbacks.

November 19, 2009 at 10:35 pm #259620

manucorporat
Participant
@manucorporat

Director.m::mainloop

- (void) mainLoop
{
/* clear window */
//glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
/* calculate "global" dt */
[self calculateDeltaTime];
if( ! isPaused_ )
[[Scheduler sharedScheduler] tick: dt];

/* clear window */
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //<<<<New position

/* to avoid flickr, nextScene MUST be here: after tick and before draw */
if( nextScene )
[self setNextScene];

glPushMatrix();

[self applyLandscape];

/* draw the scene */
[runningScene_ visit];
if( displayFPS )
[self showFPS];

glPopMatrix();

/* swap buffers */
[openGLView_ swapBuffers];
}

I have move glClear after scheduler dispatch.

What is the problem?

I have tested TransitionsTest, Sprites, drawPrimitives and they show succesfully.

November 19, 2009 at 11:33 pm #259621

nsxdavid
@nsxdavid

It would only be a problem if someone did draw calls in their scheduled callbacks. Although this is discouraged, I don’t know if it was made into law by Riq. Also, some people put debugging drawing temporarily in their perframes and such so visualize various things like vetors, motion, collisions or whatever. It’s very convenient.

November 20, 2009 at 12:12 am #259622

manucorporat
Participant
@manucorporat

I think that’s really not a problem, because surely in the final version of your game doesn’t run drawing requests into a scheduler.

Furthermore, drawing behind a scheduler is difficult because the transformations do not apply.

by the way, nsxdavid, Are you the developer geodefense? Right?

I love that game.

November 22, 2009 at 6:19 pm #259623

nsxdavid
@nsxdavid

I am indeed thanks.

Next game coming out: geoSpark is cocos2d all the way yo!

November 24, 2009 at 3:06 pm #259624

manucorporat
Participant
@manucorporat

@nsxdavid

if it is as good as geodefense, I’ll buy it.

I’m doing a game with graphics similar and the system of particles of the geodefense is one of the best.

Images in: http://manucorporat.blogspot.com/

Good luck.

November 26, 2009 at 9:42 pm #259625

gaminghorror
Participant
@gaminghorror

I allowed myself to clean up this code. It now works in any class, without the need to put it directly into CCDirector. It also works with cocos 1.9. I fixed a typo and generally cleaned up the spacing of parameters. Also compiles without warnings now (eg. truncation of 64-bit float because of missing ‘f’ in floating point constants).

I’ve put it in a class called Screenshot, so the function names make sense if you consider: [Screenshot takeAsTexture2D]

+(UIImage*) takeAsUIImage
{
CCDirector* director = [CCDirector sharedDirector];
CGSize size = director.displaySize;

//Create buffer for pixels
GLuint bufferLength = size.width * size.height * 4;
GLubyte* buffer = (GLubyte*)malloc(bufferLength);

//Read Pixels from OpenGL
glReadPixels(0, 0, size.width, size.height, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
//Make data provider with data.
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, buffer, bufferLength, NULL);

//Configure image
int bitsPerComponent = 8;
int bitsPerPixel = 32;
int bytesPerRow = 4 * size.width;
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
CGImageRef iref = CGImageCreate(size.width, size.height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);

uint32_t* pixels = (uint32_t*)malloc(bufferLength);
CGContextRef context = CGBitmapContextCreate(pixels, [director winSize].width, [director winSize].height, 8, [director winSize].width * 4, CGImageGetColorSpace(iref), kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);

CGContextTranslateCTM(context, 0, size.height);
CGContextScaleCTM(context, 1.0f, -1.0f);

switch (director.deviceOrientation)
{
case CCDeviceOrientationPortrait:
break;
case CCDeviceOrientationPortraitUpsideDown:
CGContextRotateCTM(context, CC_DEGREES_TO_RADIANS(180));
CGContextTranslateCTM(context, -size.width, -size.height);
break;
case CCDeviceOrientationLandscapeLeft:
CGContextRotateCTM(context, CC_DEGREES_TO_RADIANS(-90));
CGContextTranslateCTM(context, -size.height, 0);
break;
case CCDeviceOrientationLandscapeRight:
CGContextRotateCTM(context, CC_DEGREES_TO_RADIANS(90));
CGContextTranslateCTM(context, size.width * 0.5f, -size.height);
break;
}

CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, size.width, size.height), iref);
UIImage *outputImage = [UIImage imageWithCGImage:CGBitmapContextCreateImage(context)];

//Dealloc
CGDataProviderRelease(provider);
CGImageRelease(iref);
CGContextRelease(context);
free(buffer);
free(pixels);

return outputImage;
}

+(CCTexture2D*) takeAsTexture2D
{
return [CCTexture2D alloc] initWithImage:[Screenshot takeAsUIImage autorelease];
}

November 26, 2009 at 11:03 pm #259626

manucorporat
Participant
@manucorporat

Thanks for the corrections.

But the idea was that in later versions, this code is officially in cocos2D.

In addition, for the code works into a scheduler, should have a small change in the mainloop.

I have adapted your code to the director again, but I like your idea.

-(UIImage*) screenshotUIImage
{
CGSize displaySize = [self displaySize];
CGSize winSize = [self winSize];

//Create buffer for pixels
GLuint bufferLength = displaySize.width * displaySize.height * 4;
GLubyte* buffer = (GLubyte*)malloc(bufferLength);

//Read Pixels from OpenGL
glReadPixels(0, 0, displaySize.width, displaySize.height, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
//Make data provider with data.
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, buffer, bufferLength, NULL);

//Configure image
int bitsPerComponent = 8;
int bitsPerPixel = 32;
int bytesPerRow = 4 * displaySize.width;
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
CGImageRef iref = CGImageCreate(displaySize.width, displaySize.height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);

uint32_t* pixels = (uint32_t*)malloc(bufferLength);
CGContextRef context = CGBitmapContextCreate(pixels, winSize.width, winSize.height, 8, winSize.width * 4, CGImageGetColorSpace(iref), kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);

CGContextTranslateCTM(context, 0, displaySize.height);
CGContextScaleCTM(context, 1.0f, -1.0f);

switch (deviceOrientation_)
{
case CCDeviceOrientationPortrait: break;
case CCDeviceOrientationPortraitUpsideDown:
CGContextRotateCTM(context, CC_DEGREES_TO_RADIANS(180));
CGContextTranslateCTM(context, -displaySize.width, -displaySize.height);
break;
case CCDeviceOrientationLandscapeLeft:
CGContextRotateCTM(context, CC_DEGREES_TO_RADIANS(-90));
CGContextTranslateCTM(context, -displaySize.height, 0);
break;
case CCDeviceOrientationLandscapeRight:
CGContextRotateCTM(context, CC_DEGREES_TO_RADIANS(90));
CGContextTranslateCTM(context, displaySize.width * 0.5f, -displaySize.height);
break;
}

CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, displaySize.width, displaySize.height), iref);
CGImageRef imageRef = CGBitmapContextCreateImage(context);
UIImage *outputImage = [UIImage imageWithCGImage:imageRef];

//Dealloc
CGImageRelease(imageRef);
CGDataProviderRelease(provider);
CGImageRelease(iref);
CGColorSpaceRelease(colorSpaceRef);
CGContextRelease(context);
free(buffer);
free(pixels);

return outputImage;
}

- (Texture2D*) screenshotTexture {
return Texture2D alloc] initWithImage:[self screenshotUIImage;
}

November 26, 2009 at 11:25 pm #259627

gaminghorror
Participant
@gaminghorror

Small part of the screenshot function, i found and fixed two mem leaks but didn’t want to post it all again (let’s keep your Director approach, anyone who doesn’t want it in Director can figure it out from my post above i guess).

//Configure image
int bitsPerComponent = 8;
int bitsPerPixel = 32;
int bytesPerRow = 4 * size.width;
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
CGImageRef iref = CGImageCreate(size.width, size.height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);
CGColorSpaceRelease(colorSpaceRef);

uint32_t* pixels = (uint32_t*)malloc(bufferLength);
CGContextRef context = CGBitmapContextCreate(pixels, [director winSize].width, [director winSize].height, 8, [director winSize].width * 4, CGImageGetColorSpace(iref), kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);

CGContextTranslateCTM(context, 0, size.height);
CGContextScaleCTM(context, 1.0f, -1.0f);

switch (director.deviceOrientation)
{
case CCDeviceOrientationPortrait:
break;
case CCDeviceOrientationPortraitUpsideDown:
CGContextRotateCTM(context, CC_DEGREES_TO_RADIANS(180));
CGContextTranslateCTM(context, -size.width, -size.height);
break;
case CCDeviceOrientationLandscapeLeft:
CGContextRotateCTM(context, CC_DEGREES_TO_RADIANS(-90));
CGContextTranslateCTM(context, -size.height, 0);
break;
case CCDeviceOrientationLandscapeRight:
CGContextRotateCTM(context, CC_DEGREES_TO_RADIANS(90));
CGContextTranslateCTM(context, size.width * 0.5f, -size.height);
break;
}

CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, size.width, size.height), iref);
CGImageRef imageRef = CGBitmapContextCreateImage(context);
UIImage *outputImage = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);

The fixes in closer look are:

CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
...
CGColorSpaceRelease(colorSpaceRef);

and

CGImageRef imageRef = CGBitmapContextCreateImage(context);
UIImage *outputImage = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);

Remember, “Create” functions always return objects that you have the responsibility to free. I regularly find those mem leaks in source code samples … actually, i don’t find it, CLang static analyzer does this for me. ;)

See here if you want to know how the analyzer works (it keeps beating the crap out of my code so i can go in and fix it before it becomes a problem, really helpful):

http://fruitstandsoftware.com/blog/2008/08/finding-memory-leaks-with-the-llvmclang-static-analyzer/

November 26, 2009 at 11:33 pm #259628

manucorporat
Participant
@manucorporat

colorSpaceRef already had fixed in my last code, but I had forgotten imageRef

Thanks again. I will start using Clang static analyzer.

December 14, 2009 at 11:31 pm #259629

kl3pt
Participant
@kl3pt

Hello everyone,

This post has been extremely helpful for me. But I am having a small problem.

The way my app works is it generates a random image from a bunch of sprites, then I use the screenshot to store it as a UIImage.

From there I take the UIImage and build a collision map.

My problem is that all of this takes place in the init of my layer and therefore returning a black screenshot because nothing has been written to the screen yet.

Any ideas on how to clear this up?

Thanks!

February 14, 2010 at 1:17 pm #259630

gaminghorror
Participant
@gaminghorror

@kl3pt: you may have to use [self schedule ...] with a very short interval like 0.1f to give cocos2d time to render the screen. Personally i made a simple “drawNow” method in CCDirector so i can refresh the screen anytime i want to.

February 15, 2010 at 6:13 am #259631

edge17
Participant
@edge17

Apologies if I’m missing the point here, but there is a function, UIGetScreenImage() which takes a screenshot (it’s the same one invoked by holding home+lock). Apple recently allowed use of it officially (you’ll need iphone dev creds to view the forum thread)

https://devforums.apple.com/message/149553

February 15, 2010 at 5:27 pm #259632

riq
Keymaster
@admin

@edge17: nice tip.

February 15, 2010 at 11:16 pm #259633

edisonslabs
Participant
@edisonslabs

@edge17: Thanks for the tip.

The code is still useful if you only want to take a portion of the screen shot. You can easily modify the function to take in a rect that specifies the rectangle area of the screen where you want to take a screen shot. Just change the displaySize/Window origin/position/size and you’re done.

Viewing 25 posts - 1 through 25 (of 125 total)

You must be logged in to reply to this topic.