How can I crop an Image with mask and combine it with another image (background) on iPhone? (OpenGL ES 1.1 is preferred)

11,096

Here is my answer for OpenGL. The procedure would be very different for Quartz. The actual code is pretty simple, but getting it exactly right is the tricky part. I am using a GL context that is 1024X1024 with the origin in the bottom left. I'm not posting my code because it uses immediate mode which isn't available in OpenGL|ES. If you want my drawing code, let me know and I'll update my answer.

  1. Draw the mask with blending disabled.
  2. Enable blending, set the GLBlendFunc(GL_DST_COLOR, GL_ZERO) and draw the bleed through texture. My mask is white where it should bleed through. In your question it was black.
  3. Now to draw the background, set the blend function to glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_DST_COLOR) and draw the background texture.

EDIT Here is the code I describe above. Please note that this will not work on iOS since there is no immediate mode, but you should be able to get this working in Macintosh project. Once that is working you can convert it to something iOS compatible in the Macintosh project and then move that code over to your iOS project.

The renderMask() call is where the most interesting part is. renderTextures() draws the sample textures in the top row.

static GLuint color_texture;
static GLuint mask_texture;
static GLuint background_texture;

static float window_size[2];

void renderMask()
{
float texture_x=0, texture_y=0;
float x=0, y=0;

{
    glBindTexture(GL_TEXTURE_2D, mask_texture);
    
    glDisable(GL_BLEND);
    glBegin(GL_QUADS);
        glTexCoord2f(texture_x,texture_y);
        glVertex2f(x,y);
        
        glTexCoord2f(texture_x+1.0,texture_y);
        glVertex2f(x+512.0,y);
        
        glTexCoord2f(texture_x+1.0,texture_y+1.0);
        glVertex2f(x+512.0,y+512.0);
        
        glTexCoord2f(texture_x,texture_y+1.0);
        glVertex2f(x,y+512.0);
    glEnd();
}

{
    glBindTexture(GL_TEXTURE_2D, color_texture);
    glEnable(GL_BLEND);
    glBlendFunc(GL_DST_COLOR, GL_ZERO);
    glBegin(GL_QUADS);
        glTexCoord2f(texture_x,texture_y);
        glVertex2f(x,y);
        
        glTexCoord2f(texture_x+1.0,texture_y);
        glVertex2f(x+512.0,y);
        
        glTexCoord2f(texture_x+1.0,texture_y+1.0);
        glVertex2f(x+512.0,y+512.0);
        
        glTexCoord2f(texture_x,texture_y+1.0);
        glVertex2f(x,y+512.0);
    glEnd();
}

{   
    glBindTexture(GL_TEXTURE_2D, background_texture);
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_DST_COLOR);
    glBegin(GL_QUADS);
        glTexCoord2f(texture_x,texture_y);
        glVertex2f(x,y);
        
        glTexCoord2f(texture_x+1.0,texture_y);
        glVertex2f(x+512.0,y);
        
        glTexCoord2f(texture_x+1.0,texture_y+1.0);
        glVertex2f(x+512.0,y+512.0);
        
        glTexCoord2f(texture_x,texture_y+1.0);
        glVertex2f(x,y+512.0);
    glEnd();
}
}

// Draw small versions of the textures.
void renderTextures()
{
float texture_x=0, texture_y=0;
float x=0, y=532.0;
float size = 128;

{
    glBindTexture(GL_TEXTURE_2D, mask_texture);
    
    glDisable(GL_BLEND);
    glBegin(GL_QUADS);
        glTexCoord2f(texture_x,texture_y);
        glVertex2f(x,y);
        
        glTexCoord2f(texture_x+1.0,texture_y);
        glVertex2f(x+size,y);
        
        glTexCoord2f(texture_x+1.0,texture_y+1.0);
        glVertex2f(x+size,y+size);
        
        glTexCoord2f(texture_x,texture_y+1.0);
        glVertex2f(x,y+size);
    glEnd();
}

{
    glBindTexture(GL_TEXTURE_2D, color_texture);
    x = size + 16;

    glBegin(GL_QUADS);
        glTexCoord2f(texture_x,texture_y);
        glVertex2f(x,y);
        
        glTexCoord2f(texture_x+1.0,texture_y);
        glVertex2f(x+size,y);
        
        glTexCoord2f(texture_x+1.0,texture_y+1.0);
        glVertex2f(x+size,y+size);
        
        glTexCoord2f(texture_x,texture_y+1.0);
        glVertex2f(x,y+size);
    glEnd();
}

{
    glBindTexture(GL_TEXTURE_2D, background_texture);
    x = size*2 + 16*2;
    glBegin(GL_QUADS);
        glTexCoord2f(texture_x,texture_y);
        glVertex2f(x,y);
        
        glTexCoord2f(texture_x+1.0,texture_y);
        glVertex2f(x+size,y);
        
        glTexCoord2f(texture_x+1.0,texture_y+1.0);
        glVertex2f(x+size,y+size);
        
        glTexCoord2f(texture_x,texture_y+1.0);
        glVertex2f(x,y+size);
    glEnd();
}
}

void init()
{
GLdouble bounds[4];

glGetDoublev(GL_VIEWPORT, bounds);

window_size[0] = bounds[2];
window_size[1] = bounds[3];

glClearColor(0.0, 0.0, 0.0, 1.0);

glShadeModel(GL_SMOOTH);

// Load our textures...
color_texture = [[NSImage imageNamed:@"colors"] texture];
mask_texture = [[NSImage imageNamed:@"mask"] texture];
background_texture = [[NSImage imageNamed:@"background"] texture];

    
// Enable alpha blending.  We'll learn more about this later
glEnable(GL_BLEND);

glEnable(GL_TEXTURE_2D);
}

void draw()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glColor3f(1.0, 1.0, 1.0);

renderMask();
renderTextures();
}

void reshape(int width, int height)
{
glViewport(0, 0, width, height);

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0.0, width, 0.0, height);
glMatrixMode(GL_MODELVIEW);

window_size[0] = width;
window_size[1] = height;
}

This shows the my three textures drawn normally (crop, bleed through, and background) and then combined below.

image

Share:
11,096
Oleg Trakhman
Author by

Oleg Trakhman

I want to improve my english (since it's not my mother tongue), so please consider improving my questions/answers/comments. Thank you!

Updated on June 05, 2022

Comments

  • Oleg Trakhman
    Oleg Trakhman almost 2 years

    I need to combine three images the way I represent in attached file:

    enter image description here

    1) One image is background. It is 'solid' in sense it has no alpha channel.

    2) Another one is sprite. Sprite lies upon background. Sprite may have its own alpha channel, background has to be visible in places where sprite is transparent.

    3) There's a number of masks: I apply new mask to Sprite every frame. Mask isn't rectangular.

    In other words, visible pixel = pixel of background, if cropping mask corresponding color is white OR sprite is transparent; pixel of sprite otherwise (for example, corresponding mask's pixel is black).

    I'm working with cocos2d-iphone. Can I make such combination with cocos2d-iphone or with OpenGL ES 1.1? If any answer is YES, working code would be appreciated. If both answers is NO, is there another technology on iOS to make what I want (maybe Quartz2d or OpenGL ES 2.0) ?

    Mask format is not obligatory black for Sprite and white for Background. I can make Mask of required format, such as transparency for Background and white for Sprite if needed.

    P.S. There's another unanswered question of same kind: Possible to change the alpha value of certain pixels on iPhone?

  • Oleg Trakhman
    Oleg Trakhman about 13 years
    Seems it's not working :( Just imagine black spot on Sprite: after 2) we would got two types of black - black of black spot on Sprite and black of unmasked area. ON 3) Background will appear everywhere DST_COLOR is BLACK.
  • Oleg Trakhman
    Oleg Trakhman about 13 years
    I mean BLACK pixels of sprite, if they are within mask, should remain BLACK, not background.
  • Mark
    Mark about 13 years
    The mask needs to be white where it is bleeding through. The blending in step 2 multiplies the mask color value by the value for the bleed through texture. if the mask is black (0,0,0) the result of the multiplication is zero. So your cropping_mask.png needs to be white in the pentagon and transparent outside the pentagon.
  • Oleg Trakhman
    Oleg Trakhman about 13 years
    I know, after 2nd step what i will get my sprite, cropped by mask on a black background, yes?
  • Mark
    Mark about 13 years
    Right. After step 2 you should have your mask filled in with the bleed through pattern.
  • Oleg Trakhman
    Oleg Trakhman about 13 years
    I beg pardon, but if Sprite has a black pixel, covered by mask, then after 2) I'd have cropped Sprite, on black background, right? After 3) I have background everywhere on black pixels, right? After that, black pixel of my sprite become colored. It just doesn't work, I tried a much blending schemes but they all seems not appropriate for my case. Problem is that glBlend always combines 2 textures, but i need three. Is there something other then blending?
  • Mark
    Mark about 13 years
    You combine the first two textures (Sprite.png and cropping_mask.png) to get the filled shape with mask. Next you take the result from that and blend it with the background texture to get the final image. I'll post my code and some images to help. In my example the shape that you want masked needs to be white.