2009-08-30 25 views

Trả lời

1

CHỈNH SỬA: như ghi chú của người phá hủy bên dưới, bạn không còn cần phải hiển thị lớp, bạn có thể (và nên) bây giờ sử dụng mức cao hơn [UIView drawViewHierarchyInRect:]. Khác hơn thế; điều này sẽ làm việc giống nhau.

An EAGLView chỉ là một loại chế độ xem và lớp cơ bản CAEAGLLayer chỉ là một loại lớp. Điều đó có nghĩa là, standard approach để chuyển đổi chế độ xem/lớp thành UIImage sẽ hoạt động. (Thực tế là câu hỏi được liên kết là UIWebview không quan trọng; đó chỉ là một dạng xem khác.)

+3

Cảm ơn câu trả lời Rob, nhưng tôi đã thử "Standard Me "và nó không hoạt động. Mặc dù EAGLView của tôi hiển thị chính xác một texture được nạp vào nó, UIImage duy nhất mà tôi có thể trích xuất từ ​​nó bằng cách sử dụng STANDARD APPROACH là một màu trắng trống, đó chính là cách EAGLView xuất hiện trong IB. Điều này thật kỳ lạ, quả thực, nhìn thấy cách EAGLView chỉ là một dạng UIView. Tôi nghĩ rằng có lẽ tôi cần phải sử dụng glReadPixels hoặc một cái gì đó để thay thế? Tôi đang làm việc với một EAGLView từ ví dụ GLImageProcessing Mã mẫu của Apple. – RexOnRoids

+0

Eh ... Tôi thực sự đã nói quá nhanh. OpenGL không giống như Quartz. Xin lỗi vì điều đó. Tôi tin rằng chủ đề này sẽ giải quyết vấn đề của bạn. Đọc qua tất cả các tin nhắn; anh ấy đã phát hiện ra một số lỗi trên đường đi. Tôi giả sử bạn đã biết làm thế nào để đối phó với một CGContext và nhận được một CGImage ra khỏi nó (và một UIImage từ đó). http://lists.apple.com/archives/mac-opengl/2006//jan/msg00085.html –

+0

Tất cả các câu trả lời này đều lỗi thời và không cần thiết kéo dài. Giải pháp chỉ yêu cầu 4 dòng mã UIKit, hãy xem https://developer.apple.com/library/content/qa/qa1817/_index.html – demianturner

6
-(UIImage *) saveImageFromGLView 
{ 
    NSInteger myDataLength = 320 * 480 * 4; 
    // allocate array and read pixels into it. 
    GLubyte *buffer = (GLubyte *) malloc(myDataLength); 
    glReadPixels(0, 0, 320, 480, GL_RGBA, GL_UNSIGNED_BYTE, buffer); 
    // gl renders "upside down" so swap top to bottom into new array. 
    // there's gotta be a better way, but this works. 
    GLubyte *buffer2 = (GLubyte *) malloc(myDataLength); 
    for(int y = 0; y <480; y++) 
    { 
     for(int x = 0; x <320 * 4; x++) 
     { 
      buffer2[(479 - y) * 320 * 4 + x] = buffer[y * 4 * 320 + x]; 
     } 
    } 
    // make data provider with data. 
    CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, buffer2, myDataLength, NULL); 
    // prep the ingredients 
    int bitsPerComponent = 8; 
    int bitsPerPixel = 32; 
    int bytesPerRow = 4 * 320; 
    CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB(); 
    CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault; 
    CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault; 
    // make the cgimage 
    CGImageRef imageRef = CGImageCreate(320, 480, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent); 
    // then make the uiimage from that 
    UIImage *myImage = [UIImage imageWithCGImage:imageRef]; 

    CGImageRelease(imageRef); 
    CGDataProviderRelease(provider); 
    CGColorSpaceRelease(colorSpaceRef); 
    free(buffer2); 

    return myImage; 
} 
+0

Đoạn mã trên có một vài rò rỉ trong đó, vì vậy tôi khuyên bạn nên sử dụng mã hiệu quả hơn được cung cấp trong câu trả lời của Bram thay thế. –

+2

Tôi đã đi trước và kết hợp các bản sửa lỗi rò rỉ bộ nhớ quan trọng hơn từ mã của Bram, chỉ trong trường hợp các nhà phát triển trong tương lai sao chép và dán mã này vào các ứng dụng của riêng họ. –

+0

Cảm ơn bạn đã sửa lỗi rò rỉ! –

9

Đây là phiên bản được dọn dẹp của mã Quakeboy. Tôi đã thử nghiệm trên iPad và hoạt động tốt. Những cải tiến bao gồm:

  • làm việc với bất kỳ kích thước EAGLView
  • công trình với màn hình võng mạc (điểm quy mô 2)
  • thay thế vòng lặp lồng nhau với memcpy
  • dọn dẹp bộ nhớ bị rò rỉ
  • tiết kiệm UIImage trong photoalbum như một phần thưởng.

Sử dụng này như là một phương pháp trong EAGLView của bạn:

-(void)snapUIImage 
{ 
    int s = 1; 
    UIScreen* screen = [ UIScreen mainScreen ]; 
    if ([ screen respondsToSelector:@selector(scale) ]) 
     s = (int) [ screen scale ]; 

    const int w = self.frame.size.width; 
    const int h = self.frame.size.height; 
    const NSInteger myDataLength = w * h * 4 * s * s; 
    // allocate array and read pixels into it. 
    GLubyte *buffer = (GLubyte *) malloc(myDataLength); 
    glReadPixels(0, 0, w*s, h*s, GL_RGBA, GL_UNSIGNED_BYTE, buffer); 
    // gl renders "upside down" so swap top to bottom into new array. 
    // there's gotta be a better way, but this works. 
    GLubyte *buffer2 = (GLubyte *) malloc(myDataLength); 
    for(int y = 0; y < h*s; y++) 
    { 
     memcpy(buffer2 + (h*s - 1 - y) * w * 4 * s, buffer + (y * 4 * w * s), w * 4 * s); 
    } 
    free(buffer); // work with the flipped buffer, so get rid of the original one. 

    // make data provider with data. 
    CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, buffer2, myDataLength, NULL); 
    // prep the ingredients 
    int bitsPerComponent = 8; 
    int bitsPerPixel = 32; 
    int bytesPerRow = 4 * w * s; 
    CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB(); 
    CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault; 
    CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault; 
    // make the cgimage 
    CGImageRef imageRef = CGImageCreate(w*s, h*s, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent); 
    // then make the uiimage from that 
    UIImage *myImage = [ UIImage imageWithCGImage:imageRef scale:s orientation:UIImageOrientationUp ]; 
    UIImageWriteToSavedPhotosAlbum(myImage, nil, nil, nil); 
    CGImageRelease(imageRef); 
    CGDataProviderRelease(provider); 
    CGColorSpaceRelease(colorSpaceRef); 
    free(buffer2); 
} 
+0

Đoạn mã trên thiếu các bản phát hành phù hợp cho không gian màu và nhà cung cấp dữ liệu, vì vậy tôi đã thêm các mã đó vào cuối phương thức để tránh rò rỉ. –

+0

Ngoài ra, 'buffer2' thực sự cần phải được giải phóng thủ công để tránh rò rỉ. Điều này cũng đã được sửa. –

+9

Tôi đã thử điều này nhưng luôn tạo ra một hình ảnh đen không có bản vẽ, tôi có thể làm gì sai? – Brodie

6

tôi đã không thể có được câu trả lời khác ở đây để làm việc một cách chính xác đối với tôi.

Sau một vài ngày, cuối cùng tôi đã có giải pháp làm việc cho điều này. Có mã được cung cấp bởi Apple trong đó tạo ra một UIImage từ một EAGLView. Sau đó, bạn chỉ cần lật hình ảnh theo chiều dọc vì UIkit bị lộn ngược.

Phương thức cung cấp của Apple - Được sửa đổi bên trong chế độ xem bạn muốn tạo thành hình ảnh.

-(UIImage *) drawableToCGImage 
{ 
GLint backingWidth2, backingHeight2; 
//Bind the color renderbuffer used to render the OpenGL ES view 
// If your application only creates a single color renderbuffer which is already bound at this point, 
// this call is redundant, but it is needed if you're dealing with multiple renderbuffers. 
// Note, replace "_colorRenderbuffer" with the actual name of the renderbuffer object defined in your class. 
glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); 

// Get the size of the backing CAEAGLLayer 
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth2); 
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight2); 

NSInteger x = 0, y = 0, width2 = backingWidth2, height2 = backingHeight2; 
NSInteger dataLength = width2 * height2 * 4; 
GLubyte *data = (GLubyte*)malloc(dataLength * sizeof(GLubyte)); 

// Read pixel data from the framebuffer 
glPixelStorei(GL_PACK_ALIGNMENT, 4); 
glReadPixels(x, y, width2, height2, GL_RGBA, GL_UNSIGNED_BYTE, data); 

// Create a CGImage with the pixel data 
// If your OpenGL ES content is opaque, use kCGImageAlphaNoneSkipLast to ignore the alpha channel 
// otherwise, use kCGImageAlphaPremultipliedLast 
CGDataProviderRef ref = CGDataProviderCreateWithData(NULL, data, dataLength, NULL); 
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB(); 
CGImageRef iref = CGImageCreate(width2, height2, 8, 32, width2 * 4, colorspace, kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast, 
           ref, NULL, true, kCGRenderingIntentDefault); 

// OpenGL ES measures data in PIXELS 
// Create a graphics context with the target size measured in POINTS 
NSInteger widthInPoints, heightInPoints; 
if (NULL != UIGraphicsBeginImageContextWithOptions) { 
    // On iOS 4 and later, use UIGraphicsBeginImageContextWithOptions to take the scale into consideration 
    // Set the scale parameter to your OpenGL ES view's contentScaleFactor 
    // so that you get a high-resolution snapshot when its value is greater than 1.0 
    CGFloat scale = self.contentScaleFactor; 
    widthInPoints = width2/scale; 
    heightInPoints = height2/scale; 
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(widthInPoints, heightInPoints), NO, scale); 
} 
else { 
    // On iOS prior to 4, fall back to use UIGraphicsBeginImageContext 
    widthInPoints = width2; 
    heightInPoints = height2; 
    UIGraphicsBeginImageContext(CGSizeMake(widthInPoints, heightInPoints)); 
} 

CGContextRef cgcontext = UIGraphicsGetCurrentContext(); 

// UIKit coordinate system is upside down to GL/Quartz coordinate system 
// Flip the CGImage by rendering it to the flipped bitmap context 
// The size of the destination area is measured in POINTS 
CGContextSetBlendMode(cgcontext, kCGBlendModeCopy); 
CGContextDrawImage(cgcontext, CGRectMake(0.0, 0.0, widthInPoints, heightInPoints), iref); 

// Retrieve the UIImage from the current context 
UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 

UIGraphicsEndImageContext(); 

// Clean up 
free(data); 
CFRelease(ref); 
CFRelease(colorspace); 
CGImageRelease(iref); 

return image; 

}

Và heres một phương pháp để lật hình ảnh

- (UIImage *) flipImageVertically:(UIImage *)originalImage { 
UIImageView *tempImageView = [[UIImageView alloc] initWithImage:originalImage]; 
UIGraphicsBeginImageContext(tempImageView.frame.size); 
CGContextRef context = UIGraphicsGetCurrentContext(); 
CGAffineTransform flipVertical = CGAffineTransformMake(
                 1, 0, 0, -1, 0, tempImageView.frame.size.height 
                 ); 
CGContextConcatCTM(context, flipVertical); 

[tempImageView.layer renderInContext:context]; 

UIImage *flippedImage = UIGraphicsGetImageFromCurrentImageContext(); 
UIGraphicsEndImageContext(); 
//[tempImageView release]; 

return flippedImage; 

}

Và đây là một liên kết đến trang dev của Apple nơi tôi tìm thấy phương pháp đầu tiên để tham khảo. http://developer.apple.com/library/ios/#qa/qa1704/_index.html

+0

Rất tiếc, tôi đã chuyển câu trả lời của bạn từ câu hỏi đó (câu hỏi trùng lặp này) vào đây để mọi người có một nguồn truy cập cho nhiều cách khác nhau để chụp từ OpenGL Cảnh ES. –

+0

@Nate: Có cách nào để biết liệu có bất kỳ nội dung nào được viết nguệch ngoạc trên EAGLView hay không. Tôi cũng đang cố chụp ảnh màn hình như đã mô tả ở trên nhưng nếu người dùng chụp màn hình hoặc xóa màn hình và chụp màn hình thì màn hình trống sẽ bị chụp mà tôi muốn tránh. –

0

CGDataProviderCreateWithData đi kèm với một callback phát hành để giải phóng dữ liệu, nơi bạn nên làm khi phát hành:

void releaseBufferData(void *info, const void *data, size_t size) 
{ 
    free((void*)data); 
} 

Sau đó thực hiện điều này như ví dụ khác, nhưng KHÔNG dữ liệu miễn phí tại đây:

GLubyte *bufferData = (GLubyte *) malloc(bufferDataSize); 
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, bufferData, bufferDataSize, releaseBufferData); 
.... 
CGDataProviderRelease(provider); 

Hoặc chỉ cần sử dụng CGDataProviderCreateWithCFData mà không cần phát hành lại công cụ gọi lại thay thế:

GLubyte *bufferData = (GLubyte *) malloc(bufferDataSize); 
NSData *data = [NSData dataWithBytes:bufferData length:bufferDataSize]; 
CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)data); 
.... 
CGDataProviderRelease(provider); 
free(bufferData); // Remember to free it 

Để biết thêm thông tin, xin vui lòng kiểm tra này thảo luận:

What's the right memory management pattern for buffer->CGImageRef->UIImage?

+0

Điều này là chính xác; chỉ sử dụng gọi lại để giải phóng dữ liệu trong ngữ cảnh Đồ họa cốt lõi. –

0

Với mã trên này của Brad Larson, bạn phải chỉnh sửa EAGLView.m bạn

- (id)initWithCoder:(NSCoder*)coder{ 
    self = [super initWithCoder:coder]; 
    if (self) { 
     CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer; 
     eaglLayer.opaque = TRUE; 
     eaglLayer.drawableProperties = 
      [NSDictionary dictionaryWithObjectsAndKeys: 
       [NSNumber numberWithBool:YES], kEAGLDrawablePropertyRetainedBacking, 
       kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil]; 
    } 
    return self; 
} 

Bạn phải thay đổi numberWithBool giá trị YES

Các vấn đề liên quan