I'm generating an image using quartz2d and I want to use it as an opengl texture.
The tricky part is that I want to use as few bits per pixel as possible, so I'm creating cgContext as following:
int bitsPerComponent = 5;
int bytesPerPixel = 2;
int width = 1024;
int height = 1024;
void* imageData = malloc(width * height * bytesPerPixel);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGImageContext context = CGBitmapContextCreate(imageData, width, height, bitsPerComponent, width * bytesPerPixel, colorSpace, kCGImageAlphaNoneSkipFirst);
//draw things into context, release memory, etc.
As stated in the documentation here, this is the only supported RGB pixel format for CGBitmapContextCreate which uses 16 bits per pixel.
So now I want to upload this imageData which looks like "1 bit skipped - 5 bits red - 5 bits green - 5 bits blue" into an opengl texture. So I should do something like this:
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, imageData);
That won't work because in this call I've specified pixel format as 5 red - 5 green - 5 blue - 1 alpha. That is wrong, but it appears that there is no format that would match core graphics output.
There are some other options like GL_UNSIGNED_SHORT_1_5_5_5_REV, but those wont work on the iphone.
I need some way to use this imageData as a texture, but I really don't want to swap bytes around manually using memset or such, because that seems terribly inefficient.
You do need to swap bits around to get it into a denser format like RGBA551 or RGB565, since as you note, CGBitmapContext does not support these formats for drawing (for simplicity and efficency's sake).
memset isn't going to do the trick, but there are "fast" conversion routines in Accelerate.framework.
See vImageConvert_ARGB8888toRGB565(…) and vImageConvert_ARGB8888toARGB1555(…), available on iOS 5 and later.
For iOS 7.0, OS X.9 and later:
vImage_CGImageFormat fmt = {
.bitsPerComponent = 5,
.bitsPerPixel = 16,
.colorSpace = NULL, // faster with CGImageGetColorSpace(cgImage) if known to be RGB
.bitmapInfo = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder16Little // ARGB1555 little endian
};
vImage_Buffer buf;
vImageBuffer_InitWithCGImage( &buf, &fmt, NULL, cgImage, kvImageNoFlags );
...
free(buf.data);
Data is in buf.data, along with image height, width and rowBytes info. I don't recall what GL's requirements are for whether row padding is allowed. You can control that by preallocating the buf.data and buf.rowBytes fields and passing kvImageDoNotAllocate in the flags.
565_REV is kCGImageAlphaNone | kCGBitmapByteOrder16Little.
5551_REV is kCGImageAlphaNoneSkipLast | kCGBitmapByteOrder16Little
Related
I want to draw an alpha mask image in code. Right now I do:
1) Create a bitmap context using CGBitmapContextCreate with options CGColorSpaceCreateDeviceRGB and kCGImageAlphaPremultipliedFirst.
2) Then I draw into this context, using only grayscale colors like white and black.
3) Then I create a mask image from that context, using CGImageMaskCreate.
Conclusion: I waste a lot of memory! Because from my understanding, a mask image is grayscale only, right? So why create a context in ARGB in the first place.
How can I create a CGContextRef that is intended to be used for drawing a mask image? My thoughts are to use CGColorSpaceCreateDeviceGray, but here the problems start. This is the exact code how I create my ARGB bitmap context:
CGContextRef context = NULL;
CGColorSpaceRef colorSpace;
uint32_t * bitmapData;
int imageWidth = round(size.width);
int imageHeight = round(size.height);
int bitmapBytesPerRow = (imageWidth * 4);
int bitmapByteCount = (bitmapBytesPerRow * imageHeight);
colorSpace = CGColorSpaceCreateDeviceRGB();
bitmapData = malloc(bitmapByteCount);
context = CGBitmapContextCreate(bitmapData,
imageWidth,
imageHeight,
8, // bits per component
bitmapBytesPerRow,
colorSpace,
kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(colorSpace);
I am not sure how to compute the bitmapBytesPerRow for such a context. I assume it would be just imageWidth? And what must I supply for bits per component in CGBitmapContextCreate?
There is CGColorSpaceGetNumberOfComponents() but it reports only the number of components. This does not tell me how many bytes a component has.
Also what makes me nervous is that the 4 and 8 are hard-coded in my code above. Who says it's always 4 bytes per component, and who says it's 8 bits per component? I just took this from various sample codes out there. Everyone seems to do it this way. It works. But future proof? Probably not.
You would make my day with some great answers. Thanks.
Edit: I found a code-snippet, but it is confusing:
CGColorSpaceRef colorSpace2 = CGColorSpaceCreateDeviceGray();
CGContextRef gradientBitmapContext = CGBitmapContextCreate (NULL, 1, reflectRect.size.height,8, 0, colorSpace2, kCGImageAlphaNone);
Why 0 for bytes per row? The documentation does not say you can pass 0. Looks wrong.
Those parameters are telling the system how to treat the data or memory you supply. You have created that yourself, so you know what layout you intend. What, if anything, the system might want to do with it behind the scenes is not your immediate problem.
In this case, you'll provide 8 bits per sample, with just the 1 component, and probably not want to use any row padding, in which case your bytesPerRow should indeed be the same as the image width.
..Continued on from my previous question
I have a 320*480 RGB565 framebuffer which I wish to draw using OpenGL ES 1.0 on the iPhone.
- (void)setupView
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, (int[4]){0, 0, 480, 320});
glEnable(GL_TEXTURE_2D);
}
// Updates the OpenGL view when the timer fires
- (void)drawView
{
// Make sure that you are drawing to the current context
[EAGLContext setCurrentContext:context];
//Get the 320*480 buffer
const int8_t * frameBuf = [source getNextBuffer];
//Create enough storage for a 512x512 power of 2 texture
int8_t lBuf[2*512*512];
memcpy (lBuf, frameBuf, 320*480*2);
//Upload the texture
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 512, 512, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, lBuf);
//Draw it
glDrawTexiOES(0, 0, 1, 480, 320);
[context presentRenderbuffer:GL_RENDERBUFFER_OES];
}
If I produce the original texture in 512*512 the output is cropped incorrectly but other than that looks fine. However using the require output size of 320*480 everything is distorted and messed up.
I'm pretty sure it's the way I'm copying the framebuffer into the new 512*512 buffer. I have tried this routine
int8_t lBuf[512][512][2];
const char * frameDataP = frameData;
for (int ii = 0; ii < 480; ++ii) {
memcpy(lBuf[ii], frameDataP, 320);
frameDataP += 320;
}
Which is better, but the width appears to be stretched and the height is messed up.
Any help appreciated.
Are you drawing this background in portrait or landscape mode? The glDrawTexiOES parameters are (x, y, z, width, height) and since every other part of your code seems to reference those numbers as 320x480 that might be contributing to the "cropped incorrectly" problem - try switching the 320 and 480 in the cropping rectangle and the width/height in the glDrawTex call. (Might be partially my fault here for not posting the parameters in the last thread. Sorry!)
You'll have to use that second FB copy routine though. The reason the memcpy command won't work is because it will grab the 320x480x2 buffer (307200 bytes) and dump it into a 512x512x2 buffer (524288 bytes) as a single contiguous block - it won't know enough to copy 320 bytes, add 192 blocks of "dead space", and resume.
It looks like the buffer allocated isn't large enough. If you have 8 bits (1 byte) per color component, you are allocating one byte too few. This could explain why the image shows correct for only a part of the image.
I would think the following lines:
//Create enough storage for a 512x512 power of 2 texture
int8_t lBuf[2*512*512];
memcpy (lBuf, frameBuf, 320*480*2);
would need to be changed to:
//Create enough storage for a 512x512 power of 2 texture
int8_t lBuf[3*512*512];
memcpy (lBuf, frameBuf, 320*480*3);
I am using the following code (from a blog post) to resize an image
if (inImage.size.width <= inImage.size.height) {
// Portrait
ratio = inImage.size.height / inImage.size.width;
resizedRect = CGRectMake(0, 0, width, width * ratio);
}
else {
// Landscape
ratio = inImage.size.width / inImage.size.height;
resizedRect = CGRectMake(0, 0, height * ratio, height);
}
CGImageRef imageRef = [inImage CGImage];
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef);
if (alphaInfo == kCGImageAlphaNone)
alphaInfo = kCGImageAlphaNoneSkipLast;
CGContextRef bitmap = CGBitmapContextCreate(
NULL,
resizedRect.size.width, // width
resizedRect.size.height, // height
CGImageGetBitsPerComponent(imageRef), // really needs to always be 8
4 * resizedRect.size.width, // rowbytes
CGImageGetColorSpace(imageRef),
alphaInfo
);
but for some reason depending on the size I am try to resize to I get the following error generated
CGBitmapContextCreate: unsupported
parameter combination: 8 integer
bits/component; 32 bits/pixel;
3-component colorspace;
kCGImageAlphaNoneSkipFirst; XXX
bytes/row.
where XXX differs depending on which image.
The rect I am creating is propotional to the image, I take a ratio from the width/height (depending on aspect) and multiple that be target width/height.
Here are some examples (X errors, / doesnt), the resize size will be 50xX or Xx50 depending on aspect:
Source 50x50 69x69
430x320 / X
240x320 / /
272x320 / /
480x419 / X
426x320 X X
480x256 X X
Where you wrote thumbRect, did you mean resizedRect? thumbRect does not otherwise occur.
I suspect the problem is that resizedRect.size.width is non integral. Note that it's floating point.
The width and bytesPerRow parameters of CGBitmapContextCreate are declared as integers. When you pass a floating point value, such as here, it gets truncated.
Suppose your resizedRect.size.width is 1.25. Then you will end up passing 1 for the width, and floor(1.25 * 4) == 5 as the bytes per row. That's inconsistent. You always want to pass four times whatever you passed for the width for the bytes per row.
You can also just leave bytesPerRow as 0, by the way. Then the system picks the best bytesPerRow (which is often larger than 4 times the width - it pads out for alignment).
Is there a way to find out whether two CGPathRefs are intersected or not. In my case all the CGPaths are having closePath.
For example, I am having two paths. One path is the rectangle which is rotated with some angle and the other path is curved path. Two paths origin will be changing frequently. At some point they may intersect. I want to know when they are intersected. Please let me know if you have any solution.
Thanks in advance
Make one path the clipping path, draw the other path, then search for pixels that survived the clipping process:
// initialise and erase context
CGContextAddPath(context, path1);
CGContextClip(context);
// set fill colour to intersection colour
CGContextAddPath(context, path2);
CGContextFillPath(context);
// search for pixels that match intersection colour
This works because clipping = intersecting.
Don't forget that intersection depends on the definition of interiority, of which there are several. This code uses the winding-number fill rule, you might want the even odd rule or something else again. If interiority doesn't keep you up at night, then this code should be fine.
My previous answer involved drawing transparent curves to an RGBA context. This solution is superior to the old one because it is
simpler
uses a quarter of the memory as an 8bit greyscale context suffices
obviates the need for hairy, difficult-to-debug transparency code
Who could ask for more?
I guess you could ask for a complete implementation, ready to cut'n'paste, but that would spoil the fun and obfuscate an otherwise simple answer.
OLDER, HARDER TO UNDERSTAND AND LESS EFFICIENT ANSWER
Draw both CGPathRefs separately at 50% transparency into a zeroed, CGBitmapContextCreate-ed RGBA memory buffer and check for any pixel values > 128. This works on any platform that supports CoreGraphics (i.e. iOS and OSX).
In pseudocode
// zero memory
CGContextRef context;
context = CGBitmapContextCreate(memory, wide, high, 8, wide*4, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaPremultipliedLast);
CGContextSetRGBFillColor(context, 1, 1, 1, 0.5); // now everything you draw will be at 50%
// draw your path 1 to context
// draw your path 2 to context
// for each pixel in memory buffer
if(*p > 128) return true; // curves intersect
else p+= 4; // keep looking
Let the resolution of the rasterised versions be your precision and choose the precision to suit your performance needs.
1) There isn't any CGPath API to do this. But, you can do the math to figure it out. Take a look at this wikipedia article on Bezier curves to see how the curves in CGPath are implemented.
2) This is going to be slow on the iPhone I would expect but you could fill both paths into a buffer in difference colors (say, red and blue, with alpha=0.5) and then iterate through the buffer to find any pixels that occur at intersections. This will be extremely slow.
For iOS, the alpha blend seems to be ignored.
Instead, you can do a color blend, which will achieve the same effect, but doesn't need alpha:
CGContextSetBlendMode(context, kCGBlendModeColorDodge);
CGFloat semiTransparent[] = { .5,.5,.5,1};
Pixels in output Image will be:
RGB = 0,0,0 = (0.0f) ... no path
RGB = 64,64,64 = (0.25f) ... one path, no intersection
RGB = 128,128,128 = (0.5f) ... two paths, intersection found
Complete code for drawing:
-(void) drawFirst:(CGPathRef) first second:(CGPathRef) second into:(CGContextRef)context
{
/** setup the context for DODGE (everything gets lighter if it overlaps) */
CGContextSetBlendMode(context, kCGBlendModeColorDodge);
CGFloat semiTransparent[] = { .5,.5,.5,1};
CGContextSetStrokeColor(context, semiTransparent);
CGContextSetFillColor(context, semiTransparent);
CGContextAddPath(context, first);
CGContextFillPath(context);
CGContextStrokePath(context);
CGContextAddPath(context, second);
CGContextFillPath(context);
CGContextStrokePath(context);
}
Complete code for checking output:
[self drawFirst:YOUR_FIRST_PATH second:YOUR_SECOND_PATH into:context];
// Now we can get a pointer to the image data associated with the bitmap
// context.
BOOL result = FALSE;
unsigned char* data = CGBitmapContextGetData (context);
if (data != NULL) {
for( int i=0; i<width; i++ )
for( int k=0; k<width; k++ )
{
//offset locates the pixel in the data from x,y.
//4 for 4 bytes of data per pixel, w is width of one row of data.
int offset = 4*((width*round(k))+round(i));
int alpha = data[offset];
int red = data[offset+1];
int green = data[offset+2];
int blue = data[offset+3];
if( red > 254 )
{
result = TRUE;
break;
}
}
And, finally, here's a slightly modified code from another SO answer ... complete code for creating an RGB space on iOS 4, iOS 5, that will support the above functions:
- (CGContextRef) createARGBBitmapContextWithFrame:(CGRect) frame
{
/** NB: this requires iOS 4 or above - it uses the auto-allocating behaviour of Apple's method, to reduce a potential memory leak in the original StackOverflow version */
CGContextRef context = NULL;
CGColorSpaceRef colorSpace;
void * bitmapData;
int bitmapByteCount;
int bitmapBytesPerRow;
// Get image width, height. We'll use the entire image.
size_t pixelsWide = frame.size.width;
size_t pixelsHigh = frame.size.height;
// Declare the number of bytes per row. Each pixel in the bitmap in this
// example is represented by 4 bytes; 8 bits each of red, green, blue, and
// alpha.
bitmapBytesPerRow = (pixelsWide * 4);
bitmapByteCount = (bitmapBytesPerRow * pixelsHigh);
// Use the generic RGB color space.
colorSpace = CGColorSpaceCreateDeviceRGB();
if (colorSpace == NULL)
{
fprintf(stderr, "Error allocating color space\n");
return NULL;
}
// Create the bitmap context. We want pre-multiplied ARGB, 8-bits
// per component. Regardless of what the source image format is
// (CMYK, Grayscale, and so on) it will be converted over to the format
// specified here by CGBitmapContextCreate.
context = CGBitmapContextCreate (NULL,
pixelsWide,
pixelsHigh,
8, // bits per component
bitmapBytesPerRow,
colorSpace,
kCGImageAlphaPremultipliedFirst
//kCGImageAlphaFirst
);
if (context == NULL)
{
fprintf (stderr, "Context not created!");
}
// Make sure and release colorspace before returning
CGColorSpaceRelease( colorSpace );
return context;
}
I have created an context like this (simplified):
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate (bitmapData,
pixWide,
pixHeigh,
8, // bits per component
bitmapBytesPerRow,
colorSpace,
kCGImageAlphaPremultipliedFirst);
Now, when I try to extract the data for the first pixel in my PNG with Alphatransparency, it has very weird alpha values. I have an simple PNG that's a square. On each edge I cut off 10x10 pixel and made them totally transparent. Alpha shouldn't be something like 153 there.
There's an kCGImageAlphaOnly declared in CGImage.h. The doc says:
kCGImageAlphaOnly There is no color
data, only an alpha channel.
Ok, so that actually sounds good, because I only need Alpha data, and nothing else. But this raises some question marks in my head. If I do habe a fully equipped PNG with a bunch of colors + alpha: Would this constant make sure that my PNG is converted to match that color space? Or would I have to provide an PNG that matches that specified color space?
Edit: I tried using kCGImageAlphaOnly, but I get this error:
<Error>: CGBitmapContextCreate: unsupported parameter combination: 8 integer bits/component; 24 bits/pixel; 0-component colorspace; kCGImageAlphaOnly; 55 bytes/row.
What may be the problem here? I specified this before:
size_t pixelsWide = CGImageGetWidth(inImage);
size_t pixelsHigh = CGImageGetHeight(inImage);
bitmapBytesPerRow = (pixelsWide * 1); // not * 4, because I just want alpha
bitmapByteCount = (bitmapBytesPerRow * pixelsHigh);
Edit: I've been reading this a minute ago:
PNG's which are added to XCode are
optimized by 'pngcrush' during
compilation. This does some
byte-swapping (from RGBA to BRGA) and
pre-multiplication of alpha.
I assume that this pre-multiplication of alpha makes trouble.
Edit: The alpha channel keeps intact after pngcrunch did the byte-swapping stuff to the PNG. Since I don't care about colors, just alpha, that pre-multiplication shouldn't be a too big problem, I think.
My PNG's have been 24bit PNG bevor I added them to Xcode.
You can't do this:
bitmapBytesPerRow = (pixelsWide * 1); // not * 4, because I just want alpha
The function you're calling will always return all the image data. The kCGImageAlphaOnly constant is used to tell YOU that an image only contains an alpha channel, no colour information.
You'll need to use pixelsWide * 4 for the bytesPerRow. Also note that the bitmapData argument to CGBitmapContextCreate() is used to provide storage space explicitly, rather than having it drawn for you.
Possibly what you want to do is this (untested code, just typed from memory):
CGImageRef image = GetMyImageFromWhereverItIs();
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
CGContextRef ctx = CGBitmapContextCreate( NULL, CGImageGetWidth(image),
CGImageGetHeight(image), CGImageGetBitsPerComponent(image),
CGImageGetBytesPerRow(image), space,
kCGBitmapByteOrderDefault | kCGImageAlphaLast );
CGColorSpaceRelease( space );
// now draw the image into the context
CGRect rect = CGRectMake( 0, 0, CGImageGetWidth(image), CGImageGetHeight(image) );
CGContextDrawImage( ctx, rect, image );
UInt32 * pixels = CGBitmapContextGetData( ctx );
// now we can iterate through the data & read the alpha values
int i, count = CGBitmapContextGetBytesPerRow(ctx) * CGBitmapContextGetHeight(ctx);
for ( i = 0; i < count; i++ )
{
UInt8 alpha = pixels[i] & 0x000000ff;
// do with the alpha what you will
}
Are you sure you're looking only at alpha values?
If you're expecting all the alpha components to come first, then all the red components, etc.: That's planar layout, and I don't think Quartz supports it natively—it only supports all the components together in each pixel (ARGBARGBARGBARGB…, not AAAA…RRRR…GGGG…BBBB…). So if you're just marching straight into the data treating every byte as alpha, that's your problem: you're looking at red, green, and blue components and treating them as alpha.
As for premultiplication, that doesn't affect the alpha channel, it affects the color channels. The formula for raster compositing (putting one raster image over another) is:
dst.r = src.r * src.a + dst.r * (1.0 - src.a);
dst.g = src.g * src.a + dst.g * (1.0 - src.a);
dst.b = src.b * src.a + dst.b * (1.0 - src.a);
Premultiplication cuts out the first multiplication expression:
dst.r = src.r′ + dst.r * (1.0 - src.a);
dst.g = src.g′ + dst.g * (1.0 - src.a);
dst.b = src.b′ + dst.b * (1.0 - src.a);
This works because the source color components are already multiplied by the alpha component—hence the name “premultiplied”. It doesn't need to multiply them now, because it already has the results.
It's an optimization, and presumably an important one on the iPhone (all those multiplication operations add up when you do a million or two of them). But it doesn't affect the layout of the components: interleaved remains interleaved, whether the RGB components are premultiplied or not.