packages feed

FreeTypeGL-0.0.1: src/texture-atlas.c

/* ============================================================================
 * Freetype GL - A C OpenGL Freetype engine
 * Platform:    Any
 * WWW:         http://code.google.com/p/freetype-gl/
 * ----------------------------------------------------------------------------
 * Copyright 2011,2012 Nicolas P. Rougier. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY NICOLAS P. ROUGIER ''AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL NICOLAS P. ROUGIER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are
 * those of the authors and should not be interpreted as representing official
 * policies, either expressed or implied, of Nicolas P. Rougier.
 * ============================================================================
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <limits.h>
#include "texture-atlas.h"


// ------------------------------------------------------ texture_atlas_new ---
void texture_atlas_init( texture_atlas_t *self, const ivec2 *size, const size_t depth )
{
    assert( (depth == 1) || (depth == 3) || (depth == 4) );
    self->nodes = vector_new( sizeof(ivec3) );
    self->used = 0;
    self->size = *size;
    self->depth = depth;
    self->id = 0;
    self->uploaded = false;

    // We want a one pixel border around the whole atlas to avoid any artefact when
    // sampling texture
    ivec3 node = {{1,1,size->x-2}};

    vector_push_back( self->nodes, &node );
    self->data = (unsigned char *)
        calloc( size->x*size->y*depth, sizeof(unsigned char) );
    assert(self->data);
}


// --------------------------------------------------- texture_atlas_delete ---
void texture_atlas_fini(texture_atlas_t *self)
{
    vector_delete(self->nodes);
    free( self->data );
    if(self->id) glDeleteTextures( 1, &self->id );
}


static void set_region(
    texture_atlas_t * self,
    ivec4 region,
    const unsigned char * data,
    size_t stride )
{
    assert(self);
    assert(region.x > 0);
    assert(region.y > 0);
    assert(region.x + region.width < self->size.x);
    assert(region.y + region.height < self->size.y);

    if(!region.width) return;

    size_t i;
    size_t depth = self->depth;
    size_t charsize = sizeof(char);
    for(i=0; i<region.height; ++i) {
        memcpy( self->data+((region.y+i)*self->size.x + region.x ) * charsize * depth,
                data + (i*stride) * charsize, region.width * charsize * depth  );
    }
}


// ------------------------------------------------------ texture_atlas_fit ---
int
texture_atlas_fit( texture_atlas_t * self,
                   const size_t index,
                   const size_t width,
                   const size_t height )
{
    assert( self );

    ivec3 *node = (ivec3 *) (vector_get( self->nodes, index ));
    int x = node->x, y, width_left = width;
    size_t i = index;

    if ( (x + width) > (self->size.x-1) )
    {
        return -1;
    }
    y = node->y;
    while( width_left > 0 )
    {
        node = (ivec3 *) (vector_get( self->nodes, i ));
        if( node->y > y )
        {
            y = node->y;
        }
        if( (y + height) > (self->size.y-1) )
        {
            return -1;
        }
        width_left -= node->z;
        ++i;
    }
    return y;
}


// ---------------------------------------------------- texture_atlas_merge ---
void
texture_atlas_merge( texture_atlas_t * self )
{
    assert( self );

    ivec3 *node, *next;
    size_t i;

    for( i=0; i< self->nodes->size-1; ++i )
    {
        node = (ivec3 *) (vector_get( self->nodes, i ));
        next = (ivec3 *) (vector_get( self->nodes, i+1 ));
        if( node->y == next->y )
        {
            node->z += next->z;
            vector_erase( self->nodes, i+1 );
            --i;
        }
    }
}


ivec4 texture_atlas_make_region(
    texture_atlas_t *self,
    size_t narrow_width,
    size_t narrow_height,
    const unsigned char *data,
    size_t stride)
{
    // We want each glyph to be separated by at least one black pixel:
    size_t width = narrow_width + 1;
    size_t height = narrow_height + 1;
    assert( self );

    ivec4 region = {{0,0,narrow_width,narrow_height}};

    int best_height = INT_MAX;
    int best_index  = -1;
    int best_width = INT_MAX;

    size_t i;
    for(i=0; i<self->nodes->size; ++i) {
        int y = texture_atlas_fit(self, i, width, height);
        if( y >= 0 ) {
            ivec3 *node = (ivec3 *) vector_get( self->nodes, i );
            if( ( (y + height) < best_height ) ||
                ( ((y + height) == best_height) && (node->z < best_width)) )
            {
                best_height = y + height;
                best_index = i;
                best_width = node->z;
                region.x = node->x;
                region.y = y;
            }
        }
    }

    if( best_index == -1 ) {
        region.x = -1;
        region.y = -1;
        region.width = 0;
        region.height = 0;
        fprintf( stderr, "Texture atlas is full (line %d)\n",  __LINE__ );
        return region;
    }

    {
        ivec3 node =
            { { region.x
              , region.y + height
              , width } };
        vector_insert( self->nodes, best_index, &node );
    }

    for(i = best_index+1; i < self->nodes->size; ++i)
    {
        ivec3 *node = (ivec3 *) vector_get( self->nodes, i );
        ivec3 *prev = (ivec3 *) vector_get( self->nodes, i-1 );

        if (node->x >= (prev->x + prev->z) ) break;

        int shrink = prev->x + prev->z - node->x;
        node->x += shrink;
        node->z -= shrink;
        if(node->z > 0) break;

        vector_erase( self->nodes, i );
        --i;
    }
    texture_atlas_merge( self );
    self->used += width * height;

    set_region(self, region, data, stride);
    self->uploaded = false;
    return region;
}


// ---------------------------------------------------- texture_atlas_clear ---
void texture_atlas_clear( texture_atlas_t * self )
{
    assert( self );

    vector_clear( self->nodes );
    self->used = 0;
    // We want a one pixel border around the whole atlas to avoid any artefact when
    // sampling texture
    ivec3 node = {{1,1,self->size.x-2}};
    vector_push_back( self->nodes, &node );
    memset( self->data, 0, self->size.x*self->size.y*self->depth );

    self->uploaded = false;
}


// --------------------------------------------------- texture_atlas_upload ---
void
texture_atlas_upload( texture_atlas_t * self )
{
    if(self->uploaded) return;
    if(!self->id) glGenTextures( 1, &self->id );

    glBindTexture( GL_TEXTURE_2D, self->id );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    if( self->depth == 4 )
    {
#ifdef GL_UNSIGNED_INT_8_8_8_8_REV
        glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, self->size.x, self->size.y,
                      0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, self->data );
#else
        glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, self->size.x, self->size.y,
                      0, GL_RGBA, GL_UNSIGNED_BYTE, self->data );
#endif
    }
    else if( self->depth == 3 )
    {
        glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, self->size.x, self->size.y,
                      0, GL_RGB, GL_UNSIGNED_BYTE, self->data );
    }
    else
    {
        glTexImage2D( GL_TEXTURE_2D, 0, GL_ALPHA, self->size.x, self->size.y,
                      0, GL_ALPHA, GL_UNSIGNED_BYTE, self->data );
    }
    self->uploaded = true;
}

void
texture_atlas_render( texture_atlas_t * self,
                      float x, float y,
                      float width, float height )
{
    glEnable( GL_BLEND );
    glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );

    glEnable( GL_TEXTURE_2D );
    glColor4f(0,0,0,1);
    glBindTexture( GL_TEXTURE_2D, self->id );

    glBegin(GL_QUADS);
    glTexCoord2f( 0, 1 ); glVertex2i( 0, 0 );
    glTexCoord2f( 0, 0 ); glVertex2i( 0, height );
    glTexCoord2f( 1, 0 ); glVertex2i( width, height );
    glTexCoord2f( 1, 1 ); glVertex2i( width, 0 );
    glEnd();
}