I am trying to render to a texture using openGL ES (for iPhone) and then display the texture on the screen. Everything works except that there is a 32 row gap at the top of the texture and the bottom 32 rows are cut off. It's like all of my drawing is being offset 32 pixels down, which results in the bottom 32 rows not being drawn as they are outside of the texture.
Here's a very simple example:
void RenderToTexture( int texture )
{
unsigned char buffer[4 * 320 * 480];
unsigned char colour[4];
colour[0] = 255;
colour[1] = 0;
colour[2] = 0;
colour[3] = 128;
for ( int i = 0; i < 4 * 320 * 480; i += 4 )
{
buffer[i] = colour[0];
buffer[i+1] = colour[1];
buffer[i+2] = colour[2];
buffer[i+3] = colour[3];
}
glBindTexture( GL_TEXTURE_2D, texture );
glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 320, 480, GL_RGBA, GL_UNSIGNED_BYTE, buffer );
}
And here's the result:
Just setting the colour using glColor4f() instead of calling RenderToTexture() results in a red screen as expected.
That 32 pixels are the missing ones to 512: 512 - 480 = 32.
The reason is you can use only texture sizes that are powers of two with GL_TEXTURE_2D.
So you have to round up your width and height to 512. You can still display only the part of the texture you want by using texture coordinates or by setting a texture matrix.
The iPhone 3GS supports non power-of-two textures under certain conditions. All the following must be true:
GL_TEXTURE_WRAP_S should be set to GL_CLAMP_TO_EDGE
GL_TEXTURE_WRAP_T should be set to GL_CLAMP_TO_EDGE
Mipmapping must be turned off; minify with GL_LINEAR rather than GL_LINEAR_MIPMAP_LINEAR
Related
I got an arbitrary shaped curve, enclosing some area. I would like to approximate the number of pixels that the curve is enclosing on an iPhone/iPad screen. How can I do so?
A curve is defined as a successive x/y coordinates of points.
A curve is closed.
A curve is drawn by a user's touches (touchesMoved method), and I
have no knowledge of what it looks like
I was thinking of somehow filling the closed curve with color, then calculating the number of pixels of this color in a screenshot of a screen. This means I need to know how to programmatically fill a closed curve with color.
Is there some other way that I'm not thinking of?
Thank you!
Let's do this by creating a Quartz path enclosing your curve. Then we'll create a bitmap context and fill the path in that context. Then we can examine the bitmap and count the pixels that were filled. We'll wrap this all in a convenient function:
static double areaOfCurveWithPoints(const CGPoint *points, size_t count) {
First we need to create the path:
CGPathRef path = createClosedPathWithPoints(points, count);
Then we need to get the bounding box of the path. CGPoint coordinates don't have to be integers, but a bitmap has to have integer dimensions, so we'll get an integral bounding box at least as big as the path's bounding box:
CGRect frame = integralFrameForPath(path);
We also need to decide how wide (in bytes) to make the bitmap:
size_t bytesPerRow = bytesPerRowForWidth(frame.size.width);
Now we can create the bitmap:
CGContextRef gc = createBitmapContextWithFrame(frame, bytesPerRow);
The bitmap is filled with black when it's created. We'll fill the path with white:
CGContextSetFillColorWithColor(gc, [UIColor whiteColor].CGColor);
CGContextAddPath(gc, path);
CGContextFillPath(gc);
Now we're done with the path so we can release it:
CGPathRelease(path);
Next we'll compute the area that was filled:
double area = areaFilledInBitmapContext(gc);
Now we're done with the bitmap context, so we can release it:
CGContextRelease(gc);
Finally, we can return the area we computed:
return area;
}
Well, that was easy! But we have to write all those helper functions. Let's start at the top. Creating the path is trivial:
static CGPathRef createClosedPathWithPoints(const CGPoint *points, size_t count) {
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddLines(path, NULL, points, count);
CGPathCloseSubpath(path);
return path;
}
Getting the integral bounding box of the path is also trivial:
static CGRect integralFrameForPath(CGPathRef path) {
CGRect frame = CGPathGetBoundingBox(path);
return CGRectIntegral(frame);
}
To choose the bytes per row of the bitmap, we could just use width of the path's bounding box. But I think Quartz likes to have bitmaps that are multiples of a nice power of two. I haven't done any testing on this, so you might want to experiment. For now, we'll round up the width to the next smallest multiple of 64:
static size_t bytesPerRowForWidth(CGFloat width) {
static const size_t kFactor = 64;
// Round up to a multiple of kFactor, which must be a power of 2.
return ((size_t)width + (kFactor - 1)) & ~(kFactor - 1);
}
We create the bitmap context with the computed sizes. We also need to translate the origin of the coordinate system. Why? Because the origin of the path's bounding box might not be at (0, 0).
static CGContextRef createBitmapContextWithFrame(CGRect frame, size_t bytesPerRow) {
CGColorSpaceRef grayscale = CGColorSpaceCreateDeviceGray();
CGContextRef gc = CGBitmapContextCreate(NULL, frame.size.width, frame.size.height, 8, bytesPerRow, grayscale, kCGImageAlphaNone);
CGColorSpaceRelease(grayscale);
CGContextTranslateCTM(gc, -frame.origin.x, -frame.origin.x);
return gc;
}
Finally, we need to write the helper that actually counts the filled pixels. We have to decide how we want to count pixels. Each pixel is represented by one unsigned 8-bit integer. A black pixel is 0. A white pixel is 255. The numbers in between are shades of gray. Quartz anti-aliases the edge of the path when it fills it using gray pixels. So we have to decide how to count those gray pixels.
One way is to define a threshold, like 128. Any pixel at or above the threshold counts as filled; the rest count as unfilled.
Another way is to count the gray pixels as partially filled, and add up that partial filling. So two exactly half-filled pixels get combined and count as a single, entirely-filled pixel. Let's do it that way:
static double areaFilledInBitmapContext(gc) {
size_t width = CGBitmapContextGetWidth(gc);
size_t height = CGBitmapContextGetHeight(gc);
size_t stride = CGBitmapContextGetBytesPerRow(gc);
uint8_t *pixels = CGBitmapContextGetData(gc);
uint64_t coverage = 0;
for (size_t y = 0; y < height; ++y) {
for (size_t x = 0; x < width; ++x) {
coverage += pixels[y * stride + x];
}
}
return (double)coverage / UINT8_MAX;
}
You can find all of the code bundled up in this gist.
I would grab the drawing as a CGIMage ...
(CGBitmapContextCreateImage(UIGraphicsGetCurrentContext());
Then, as recommended above use a "Flood Fill" approach to count the pixels.
(Google Flood Fill)
I would appreciate some help with the following. I'm trying to render a ring shape on top of another object in OpenGL ES 1.1 for an iPhone game. The ring is essentially the difference between two circles.
I have a graphic prepared for the ring itself, which is transparent in the centre.
I had hoped to just create a circle, and apply the texture to that. The texture is a picture of the ring that occupies the full size of the texture (i.e. the outside of the ring touches the four sides of the texture). The centre of the ring is transparent in the graphic being used.
It needs to be transparent in the centre to let the object underneath show through. The ring is rendering correctly, but is a solid black mass in the centre, not transparent. I'd appreciate any help to solve this.
Code that I'm using to render the circle is as follows (not optimised at all: I will move the coords in proper buffers etc for later code, but I have written it this way to just try and get it working...)
if (!m_circleEffects.empty())
{
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glEnable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);
int segments = 360;
for (int i = 0; i < m_circleEffects.size(); i++)
{
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(m_circleEffects[i].position.x, m_circleEffects[i].position.y, 0);
glBindTexture(GL_TEXTURE_2D, m_Texture);
float radius = 1.764706;
GLfloat circlePoints[segments * 3];
GLfloat textureCoords[segments * 2];
int circCount = 3;
int texCount = 2;
for (GLfloat i = 0; i < 360.0f; i += (360.0f / segments))
{
GLfloat pos1 = cosf(i * M_PI / 180);
GLfloat pos2 = sinf(i * M_PI / 180);
circlePoints[circCount] = pos1 * radius;
circlePoints[circCount+1] = pos2 * radius;
circlePoints[circCount+2] = (float)z + 5.0f;
circCount += 3;
textureCoords[texCount] = pos1 * 0.5 + 0.5;
textureCoords[texCount+1] = pos2 * 0.5 + 0.5;
texCount += 2;
}
glVertexPointer(3, GL_FLOAT, 0, circlePoints);
glTexCoordPointer(2, GL_FLOAT, 0, textureCoords);
glDrawArrays(GL_TRIANGLE_FAN, 0, segments);
}
m_circleEffects.clear();
glDisable(GL_TEXTURE_2D);
glDisable(GL_DEPTH_TEST);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
I've been experimenting with trying to create a ring rather than a circle, but I haven't been able to get this right yet.
I guess that the best approach is actually to not create a circle, but a ring, and then get the equivalent texture coordinates as well. I'm still experimenting with the width of the ring, but, it is likely that the radius of the ring is 1/4 width of the total circle.
Still a noob at OpenGL and trying to wrap my head around it. Thanks in advance for any pointers / snippets that might help.
Thanks.
What you need to do is use alpha blending, which blends colors into each other based on their alpha values (which you say are zero in the texture center, meaning transparent). So you have to enable blending by:
glEnable(GL_BLEND);
and set the standard blending functions for using a color's alpha component as opacity:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
But always keep in mind in order to see the transparent object correctly blended over the object behind, you need to render your objects in back to front order.
But if you only use the alpha as a object/no-object indicator (only values of either 0 or 1) and don't need partially transparent colors (like glass, for example), you don't need to sort your objects. In this case you should use the alpha test to discard fragments based on their alpha values, so that they don't pollute the depth-buffer and prevent the behind lying object from being rendered. An alpha test set with
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_GREATER, 0.5f);
will only render fragments (~pixels) that have an alpha of more than 0.5 and will completely discard all other fragments. If you only have alpha values of 0 (no object) or 1 (object), this is exactly what you need and in this case you don't actually need to enable blending or even sort your objects back to front.
I have particles that I want to be able to change the color of in code, so any color can be used. So I have only one texture that basically has luminance.
I've been using glColor4f(1f, 0f, 0f, 1f); to apply the color.
Every blendfunc I've tried that has come close to working ends up like the last picture below. I still want to preserve luminance, like in the middle picture. (This is like the Overlay or Soft Light filters in Photoshop, if the color layer was on top of the texture layer.)
Any ideas for how to do this without programmable shaders? Also, since these are particles, I don't want a black box behind it, I want it to add onto the scene.
Here is a solution that might be close to what you're looking for:
glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
glActiveTexture( GL_TEXTURE0 );
glEnable( GL_TEXTURE_2D );
glBindTexture(GL_TEXTURE_2D, spriteTexture);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
glActiveTexture( GL_TEXTURE1 );
glEnable( GL_TEXTURE_2D );
glBindTexture(GL_TEXTURE_2D, spriteTexture);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD );
What it does is multiply the original texture by the specified color and then adds the pixels values of the original texture on top:
final_color.rgba = original_color.rgba * color.rgba + original_color.rgba;
This will result in a brighter image than what you've asked for but might be good enough with some tweaking.
Should you want to preserve the alpha value of the texture, you'll need to use GL_COMBINE instead of GL_ADD (+ set GL_COMBINE_RGB and GL_COMBINE_ALPHA properly).
Here are some results using this technique on your texture.
NONSENSE! You don't have to use multi-texturing. Just premultiply your alpha.
If you premultiply alpha on the image after you load it in and before you create the GL texture for it then you only need one texture unit for the GL_ADD texture env mode.
If you're on iOS then Apple's libs can premultiply for you. See the example Texture2D class and look for the kCGImageAlphaPremultipliedLast flag.
If you're not using an image loader that supports premultiply then you have to do it manually after loading the image. Pseudo code:
uint8* LoadRGBAImage(const char* pImageFileName) {
Image* pImage = LoadImageData(pImageFileName);
if (pImage->eFormat != FORMAT_RGBA)
return NULL;
// allocate a buffer to store the pre-multiply result
// NOTE that in a real scenario you'll want to pad pDstData to a power-of-2
uint8* pDstData = (uint8*)malloc(pImage->rows * pImage->cols * 4);
uint8* pSrcData = pImage->pBitmapBytes;
uint32 bytesPerRow = pImage->cols * 4;
for (uint32 y = 0; y < pImage->rows; ++y) {
byte* pSrc = pSrcData + y * bytesPerRow;
byte* pDst = pDstData + y * bytesPerRow;
for (uint32 x = 0; x < pImage->cols; ++x) {
// modulate src rgb channels with alpha channel
// store result in dst rgb channels
uint8 srcAlpha = pSrc[3];
*pDst++ = Modulate(*pSrc++, srcAlpha);
*pDst++ = Modulate(*pSrc++, srcAlpha);
*pDst++ = Modulate(*pSrc++, srcAlpha);
// copy src alpha channel directly to dst alpha channel
*pDst++ = *pSrc++;
}
}
// don't forget to free() the pointer!
return pDstData;
}
uint8 Modulate(uint8 u, uint8 uControl) {
// fixed-point multiply the value u with uControl and return the result
return ((uint16)u * ((uint16)uControl + 1)) >> 8;
}
Personally, I'm using libpng and premultiplying manually.
Anyway, after you premultiply, just bind the byte data as an RGBA OpenGL texture. Using glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD); with a single texture unit should be all you need after that. You should get exactly (or pretty damn close) to what you want. You might have to use glBlendFunc(GL_SRC_ALPHA, GL_ONE); as well if you really want to make the thing look shiny btw.
This is subtly different from the Ozirus method. He's never "reducing" the RGB values of the texture by premultiplying, so the RGB channels get added too much and look sort of washed out/overly bright.
I suppose the premultiply method is more akin to Overlay whereas the Ozirus method is Soft Light.
For more, see:
http://en.wikipedia.org/wiki/Alpha_compositing
Search for "premultiplied alpha"
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
..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);