I am unable to render an image from an OpenGL context with a transparent background in CoreGraphics.
The rendered image has a black background.
This is the draw code
GLint default_frame_buffer = 0;
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &default_frame_buffer);
if (default_frame_buffer == 0) {
target = createBMGLRenderTarget(width, height);
setFiltering(target, BMGL_BilinearFiltering);
glBindFramebuffer(GL_FRAMEBUFFER, target->framebuffer);
}
glViewport(0, 0, width, height);
if (background) {
CGFloat red, green, blue, alpha;
[background getRed:&red green:&green blue:&blue alpha:&alpha];
glClearColor(red, green, blue, alpha);
} else {
glClearColor(0.f, 0.f, 0.f, 0.f);
}
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
drawer.draw()
glFlush();
glFinish();
GLubyte *data = (GLubyte *)malloc(width * height * 4);
glPixelStorei(GL_PACK_ALIGNMENT, 4);
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data);
CGDataProviderRef ref = CGDataProviderCreateWithData(NULL, data, width * height * 4, NULL);
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGImageRef imgRef = CGImageCreate(width, height, 8, 32, width * 4, colorspace, kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast, ref, NULL, NO, kCGRenderingIntentDefault);
UIGraphicsBeginImageContextWithOptions(size, YES, 0.0);
CGContextRef cgContext = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(cgContext, kCGBlendModeCopy);
CGContextDrawImage(cgContext, CGRectMake(0, 0, size.width, size.height), imgRef);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
free(data);
CGDataProviderRelease(ref);
CGColorSpaceRelease(colorspace);
CGImageRelease(imgRef);
I have tried to specifically set opaque to false as well as different blend modes but it still adds a black background, to the original clear image. I am able to set the GLKView to have a transparent background, but rendering the image and then drawing its contents into a CGImage doesn't work.
Does anyone know why this is?
I believe your problem is in how to create UIImage from raw RGBA data. To confirm this you may check what your data is on a pixel you know to be transparent. Like data[pixelIndex*4 + 3] should be zero where you expect it to be transparent. If it is not transparent then the issue is on openGL part.
Anyway the most probable reason your image is not transparent is you are premultiplying alpha using kCGImageAlphaPremultipliedLast. Try using kCGBitmapByteOrder32Big|kCGImageAlphaLast.
Related
I have a UIImage and I want to decrease the rgb value of each point , how can I do that?
or how can I change one color to another color in an image?
[Xcode8 , swift3]
This answer only applies if you want to change individual pixels..
First use UIImage.cgImage to get a CGImage. Next, create a bitmap context using CGBitmapContextCreate with the CGColorSpaceCreateDeviceRGB colour space and whatever blend modes you want.
Then call CGContextDrawImage to draw the image to the context which is backed by a pixel array provided by you. Cleanup, and you now have an array of pixels.
- (uint8_t*)getPixels:(UIImage *)image {
CGColorSpaceRef cs= CGColorSpaceCreateDeviceRGB();
uint8_t *pixels= malloc(image.size.width * image.size.height * 4);
CGContextRef ctx= CGBitmapContextCreate(rawData, image.size.width, image.size.height, 8, image.size.width * 4, cs, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(cs);
CGContextDrawImage(ctx, CGRectMake(0, 0, image.size.width, image.size.height));
CGContextRelease(ctx);
return pixels;
}
Modify the pixels however you want.. Then recreate the image from the pixels..
- (UIImage *)imageFromPixels:(uint8_t *)pixels width:(NSUInteger)width height:(NSUInteger)height {
CGDataProviderRef provider = CGDataProviderCreateWithData(nil, pixels, width * height * 4, nil);
CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
CGImageRef cgImageRef = CGImageCreate(width, height, 8, 32, width * 4, cs, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast, provider, nil, NO, kCGRenderingIntentDefault);
pixels = malloc(width * height * 4);
CGContextRef ctx = CGBitmapContextCreate(pixels, width, height, 8, width * 4, cs, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast);
CGContextDrawImage(ctx, (CGRect) { .origin.x = 0, .origin.y = 0, .size.width = width, .size.height = height }, cgImage);
CGImageRef cgImage = CGBitmapContextCreateImage(ctx);
UIImage *image = [UIImage imageWithCGImage:cgImage];
CGImageRelease(cgImage);
CGContextRelease(ctx);
CGColorSpaceRelease(cs);
CGImageRelease(cgImageRef);
CGDataProviderRelease(provider);
free(pixels);
return image;
}
One of the ways is to use image as a template and to set color which you want.
extension UIImageView {
func changeImageColor( color:UIColor) -> UIImage
{
image = image!.withRenderingMode(.alwaysTemplate)
tintColor = color
return image!
}
}
//Change color of logo
logoImage.image = logoImage.changeImageColor(color: .red)
This is my code:
#implementation UIView (LCExtension)
- (UIImage *)screenshotWithRect:(CGRect)rect {
UIGraphicsBeginImageContext(self.bounds.size);
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
image = [UIImage imageWithCGImage:CGImageCreateWithImageInRect(image.CGImage, rect)];
UIGraphicsEndImageContext();
return image;
}
#end
And I set a breakpoint at here:
Run:
This is view-self: (picture from OpenGL Rendering)
This is generated picture:
Why it's a black picture?
When working with OpenGL (ES), yeah, I assuming you are using OpenGL ES because you have a UIView category, remember that capture screen right before presenting your renderbuffers. Once you or subclass of GLKViewController present renderbuffer, it become front framebuffer, then the screen framebuffer become back framebuffer, which gives you nothing except the color you set with glClearColor.
[EAGLContext presentRenderbuffer:]
Here is a sample code for capturing screen.
- (UIImage *)createImageFromFramebuffer {
GLint params[10];
glGetIntegerv(GL_VIEWPORT, params);
int width = params[2];
int height = params[3];
const int renderTargetWidth = width;
const int renderTargetHeight = height;
const int renderTargetSize = renderTargetWidth*renderTargetHeight * 4;
uint8_t* imageBuffer = (uint8_t*)malloc(renderTargetSize);
glReadPixels(params[0], params[1],
renderTargetWidth, renderTargetHeight,
GL_RGBA, GL_UNSIGNED_BYTE, imageBuffer);
const int rowSize = renderTargetWidth*4;
CGDataProviderRef ref = CGDataProviderCreateWithData(NULL, imageBuffer, renderTargetSize, NULL);
CGImageRef iref = CGImageCreate(renderTargetWidth, renderTargetHeight, 8, 32, rowSize,
CGColorSpaceCreateDeviceRGB(),
kCGImageAlphaLast | kCGBitmapByteOrderDefault, ref,
NULL, true, kCGRenderingIntentDefault);
uint8_t* contextBuffer = (uint8_t*)malloc(renderTargetSize);
memset(contextBuffer, 0, renderTargetSize);
CGContextRef context = CGBitmapContextCreate(contextBuffer, renderTargetWidth, renderTargetHeight, 8, rowSize,
CGImageGetColorSpace(iref),
kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big);
CGContextTranslateCTM(context, 0.0, renderTargetHeight);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextDrawImage(context, CGRectMake(0.0, 0.0, renderTargetWidth, renderTargetHeight), iref);
CGImageRef outputRef = CGBitmapContextCreateImage(context);
UIImage* image = [[UIImage alloc] initWithCGImage:outputRef];
CGImageRelease(outputRef);
CGContextRelease(context);
CGImageRelease(iref);
CGDataProviderRelease(ref);
free(contextBuffer);
free(imageBuffer);
return image;
}
I draw a figure on CAEAGLLayer
glDisable(GL_DEPTH_TEST);
glEnable(GL_POINT_SPRITE_OES);
glEnable(GL_POINT_SMOOTH);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR);
glClearColor(1.0, 1.0, 1.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
It looks like this: src image
After I took this picture context this code:
- (UIImage*)snapshot:(UIView*)eaglview
{
GLint backingWidth, backingHeight;
// 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, _colorRenderbuffer);
// Get the size of the backing CAEAGLLayer
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
NSInteger x = 0, y = 0, width = backingWidth, height = backingHeight;
NSInteger dataLength = width * height * 4;
GLubyte *data = (GLubyte*)malloc(dataLength * sizeof(GLubyte));
// Read pixel data from the framebuffer
glPixelStorei(GL_PACK_ALIGNMENT, 4);
glReadPixels(x, y, width, height, 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(width, height, 8, 32, width * 4, colorspace, kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast,
ref, NULL, true, kCGRenderingIntentDefault);
аnd getting here is distortion in color: dst image.
What could be the problem?
If I change
glClearColor (1.0, 1.0, 1.0, 0.0);
on
glClearColor (0.0, 0.0, 0.0, 0.0);
screenshot context is obtained without color distortion. Only the color of a shape other. dst image
glEnable(GL_POINT_SPRITE_OES);
glEnable(GL_POINT_SMOOTH);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR);
//glClearColor(1.0, 1.0, 1.0, 0.0);
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glViewport(0, 0, self.frame.size.width, self.frame.size.height);
glUseProgram(programHandle);
I wonder if your blend equation is wrong - is this better?
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Here's the question in brief:
For some layer compositing, I have to render an OpenGL texture in a CGContext. What's the fastest way to do that?
Thoughts so far:
Obviously, calling renderInContext won't capture OpenGL content, and glReadPixels is too slow.
For some 'context', I'm calling this method in a delegate class of a layer:
- (void) drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
I've considered using a CVOpenGLESTextureCache, but that requires an additional rendering, and it seems like some complicated conversion would be necessary post-rendering.
Here's my (terrible) implemention right now:
glBindRenderbuffer(GL_RENDERBUFFER, displayRenderbuffer);
NSInteger x = 0, y = 0, width = backingWidth, height = backingHeight;
NSInteger dataLength = width * height * 4;
GLubyte *data = (GLubyte *) malloc(dataLength * sizeof(GLubyte));
glPixelStorei(GL_PACK_ALIGNMENT, 4);
glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data);
CGDataProviderRef ref = CGDataProviderCreateWithData(NULL, data, dataLength, NULL);
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGImageRef iref = CGImageCreate(width, height, 8, 32, width * 4, colorspace, kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast,
ref, NULL, true, kCGRenderingIntentDefault);
CGFloat scale = self.contentScaleFactor;
NSInteger widthInPoints, heightInPoints;
widthInPoints = width / scale;
heightInPoints = height / scale;
CGContextSetBlendMode(context, kCGBlendModeCopy);
CGContextDrawImage(context, CGRectMake(0.0, 0.0, widthInPoints, heightInPoints), iref);
// Clean up
free(data);
CFRelease(ref);
CFRelease(colorspace);
CGImageRelease(iref);
For anyone curious, the method shown above is not the fastest way.
When a UIView is asked for its contents, it will ask its layer (usually a CALayer) to draw them for it. The exception: OpenGL-based views, which use a CAEAGLLayer (a subclass of CALayer), use the same method but returns nothing. No drawing happens.
So, if you call:
[someUIView.layer drawInContext:someContext];
it will work, while
[someOpenGLView.layer drawInContext:someContext];
won't.
This also becomes an issue if you're asking a superview of any OpenGL-based view for its content: it will recursively ask each of its subviews for theirs, and any subview that uses a CAEAGLLayer will hand back nothing (you'll see a black rectangle).
I set out above to find an implementation of a delegate method of CALayer, drawLayer:inContext:, which I could use in any OpenGL-based views so that the view object itself would provide its contents (rather than the layer). The delegate method is called automatically: Apple expects it to work this way.
Where performance isn't an issue, you can implement a variation of a simple snapshot method in your view. The method would look like this:
- (void) drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
GLint backingWidth, backingHeight;
glBindRenderbufferOES(GL_RENDERBUFFER_OES, _colorRenderbuffer);
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
NSInteger x = 0, y = 0, width = backingWidth, height = backingHeight;
NSInteger dataLength = width * height * 4;
GLubyte *data = (GLubyte*)malloc(dataLength * sizeof(GLubyte));
// Read pixel data from the framebuffer
glPixelStorei(GL_PACK_ALIGNMENT, 4);
glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data);
CGDataProviderRef ref = CGDataProviderCreateWithData(NULL, data, dataLength, NULL);
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGImageRef iref = CGImageCreate(width, height, 8, 32, width * 4, colorspace, kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast,
ref, NULL, true, kCGRenderingIntentDefault);
CGContextDrawImage(ctx, self.bounds, iref);
}
BUT! This is not a performance effective.
glReadPixels, as noted just about everywhere, is not a fast call. Starting in iOS 5, Apple exposed CVOpenGLESTextureCacheRef - basically, a shared buffer that can be used both as a CVPixelBufferRef and as an OpenGL texture. Originally, it was designed to be used as a way of getting an OpenGL texture from a video frame: now it's more often used in reverse, to get a video frame from a texture.
So a much better implementation of the above idea is to use the CVPixelBufferRef you get from CVOpenGLESTextureCacheCreateTextureFromImage, get direct access to those pixels, draw them into a CGImage which you cache and which is drawn into your context in the delegate method above.
The code is here. On each rendering pass, you draw your texture into the texturecache, which is linked to the CVPixelBuffer Ref:
- (void) renderToCGImage {
// Setup the drawing
[ochrContext useProcessingContext];
glBindFramebuffer(GL_FRAMEBUFFER, layerRenderingFramebuffer);
glViewport(0, 0, (int) self.frame.size.width, (int) self.frame.size.height);
[ochrContext setActiveShaderProgram:layerRenderingShaderProgram];
// Do the actual drawing
glActiveTexture(GL_TEXTURE4);
glBindTexture(GL_TEXTURE_2D, self.inputTexture);
glUniform1i(layerRenderingInputTextureUniform, 4);
glVertexAttribPointer(layerRenderingShaderPositionAttribute, 2, GL_FLOAT, 0, 0, kRenderTargetVertices);
glVertexAttribPointer(layerRenderingShaderTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, kRenderTextureVertices);
// Draw and finish up
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glFinish();
// Try running this code asynchronously to improve performance
dispatch_async(PixelBufferReadingQueue, ^{
// Lock the base address (can't get the address without locking it).
CVPixelBufferLockBaseAddress(renderTarget, 0);
// Get a pointer to the pixels
uint32_t * pixels = (uint32_t*) CVPixelBufferGetBaseAddress(renderTarget);
// Wrap the pixel data in a data-provider object.
CGDataProviderRef pixelWrapper = CGDataProviderCreateWithData(NULL, pixels, CVPixelBufferGetDataSize(renderTarget), NULL);
// Get a color-space ref... can't this be done only once?
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
// Release the exusting CGImage
CGImageRelease(currentCGImage);
// Get a CGImage from the data (the CGImage is used in the drawLayer: delegate method above)
currentCGImage = CGImageCreate(self.frame.size.width,
self.frame.size.height,
8,
32,
4 * self.frame.size.width,
colorSpaceRef,
kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast,
pixelWrapper,
NULL,
NO,
kCGRenderingIntentDefault);
// Clean up
CVPixelBufferUnlockBaseAddress(renderTarget, 0);
CGDataProviderRelease(pixelWrapper);
CGColorSpaceRelease(colorSpaceRef);
});
}
And then implement the delegate method very simply:
- (void) drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
CGContextDrawImage(ctx, self.bounds, currentCGImage);
}
I am trying to read UIImage into the texture on iOS platform. I found the code snippet on StackOverflow that does the trick, but the problem is when I display the texture it is displayed Mirrored upside down.
int numComponents = 4;
UIImage* image = [UIImage imageNamed:#"Test.png"];
CGImageRef imageRef = [image CGImage];
int width = CGImageGetWidth(imageRef);
int height = CGImageGetHeight(imageRef);
//Allocate texture data
GLubyte* textureData = (GLubyte *)malloc(width * height * numComponents);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * width;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(textureData, width, height,
bitsPerComponent, bytesPerRow, colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGContextRelease(context);
//texture setup
GLuint textureID;
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, textureDataMirrored);
I also tried to mirror the UIImage using following line (before reading the data) but its not working either. In fact no effect whatsoever.
image = [UIImage imageWithCGImage:image.CGImage scale:image.scale orientation:UIImageOrientationUpMirrored];
Please let me know what am I doing wrong here. Thank you.
This two lines fixed the problem
CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0,height);
CGContextConcatCTM(context, flipVertical);