Вращение и компоновка файлов PNG с помощью Cairo с C

Я пишу приложение, используя Cairo в C, которое делает следующее:

  • Загрузить фон PNG (колесо)
  • повернуть колесо на 90 градусов
  • нарисовать на колесе набор чисел из других файлов PNG
  • делайте это до тех пор, пока все 6 частей колеса не будут иметь числа, нарисованные на колесе
  • сохранить PNG в файл ( results.png )

Проблема, с которой я сталкиваюсь, заключается в том, что если я не вращаю колесо, числа рисуются правильно, как показано на этом изображение, за исключением того, что колесо не вращается должным образом.

Однако, если я попытаюсь повернуть И нарисовать числа на колесе, я получу повернутое колесо без чисел, как показано на этом изображение.

Я пробовал различные перестановки кода, но я не могу заставить его работать. Я был бы признателен за некоторые подсказки и/или пример кода, который показывает, что я делаю неправильно. Я проверил документацию Cairo безрезультатно.

Код можно найти здесь.

И тут:

#include <cairo.h>
#include <stdio.h>
#include <string.h>
#include <math.h>


typedef struct
{
    int numImages;      /* Number of images in win amount string */
    int indexes[7];     /* indexes into NumberImages[] */
}WinAmountData;

/***** Function Prototypes *****************/
int InitImages( void );
void DestroyNumberImages( void );
int ParseWinAmountString( char *string, WinAmountData *amtData );
int Rotate( cairo_t *cr , cairo_surface_t *image, double degrees );
/*******************************************/


typedef struct
{
    int xOffset;    /* pixel count offsete before next digit */
    char fileName[20];
    cairo_surface_t *image; 
}ImageInfo;

ImageInfo NumberImages[] =
{
    { 8, "images/0.png", NULL },
    { 10, "images/1.png", NULL },
    { 10, "images/2.png", NULL },
    { 10, "images/3.png", NULL },
    { 10, "images/4.png", NULL },
    { 10, "images/5.png", NULL },
    { 10, "images/6.png", NULL },
    { 10, "images/7.png", NULL },
    { 10, "images/8.png", NULL },
    { 10, "images/9.png", NULL },
    { 7, "images/$.png", NULL },
    { 10, "images/euro.png", NULL },
    { 7, "images/pound.png", NULL },
    { 7, "images/yen.png", NULL }
};

enum { DOLLAR = 10, EURO, POUND, YEN };

#define FALSE 0
#define TRUE 1

int InitImages( void )
{
    int i;
    int ret = TRUE;
    cairo_status_t imgStatus;


    for( i = 0; i < ( sizeof( NumberImages ) / sizeof( ImageInfo ) ) && ret == TRUE; i++ )
    {
        NumberImages[i].image = cairo_image_surface_create_from_png( NumberImages[i].fileName );    
        imgStatus = cairo_surface_status(NumberImages[i].image);
        ret = ( CAIRO_STATUS_SUCCESS == imgStatus );
    }

    return( ret );
}


void DestroyNumberImages( void )
{
    int i;

    for( i = 0; i < ( sizeof( NumberImages ) / sizeof( ImageInfo ) ); i++ )
    {
        cairo_surface_destroy(NumberImages[i].image);
    }

    return;
}

int ParseWinAmountString( char *string, WinAmountData *amtData )
{
    int ret = TRUE;
    int i = 0, len;

    len = strlen( string );
    if( (len > 0) && (len < 8) )
    {
        amtData->numImages = len;

        for( i = 0; i < amtData->numImages && TRUE == ret; i++ )
        {
            if( string[i] >= '0' && string[i] <= '9' )
            {
                amtData->indexes[i] = string[i] - '0';
            }
            else
            {
                switch( string[i] )
                {
                    case 'D':
                        amtData->indexes[i] = DOLLAR;
                    break;

                    case 'Y':
                        amtData->indexes[i] = YEN;
                    break;

                    case 'E':
                        amtData->indexes[i] = EURO;
                    break;

                    case 'P':
                        amtData->indexes[i] = POUND;
                    break;

                    default:
                        ret = FALSE;
                    break;
                }
            }
        }
    } 
    else
    {
        ret = FALSE;
    }   

    return( ret );
}

double DegreesToRadians( double degrees )
{
    return((double)((double)degrees * ( (double)M_PI/(double)180.0 )));
}

int Rotate( cairo_t *cr , cairo_surface_t *image, double degrees )
{
    int ret = 0;

    cairo_translate(cr, 90, 90);
    cairo_rotate(cr, DegreesToRadians( degrees ));
    cairo_set_source_surface(cr, image, -90, -90);

    cairo_paint(cr);

    return ( ret );
}


int main(int argc, char *argv[])
{
    int i,x,y;
    cairo_surface_t *imgWheelBg = NULL;
    WinAmountData amtData;


    if( argc == 2 )
    {
        printf("Parsing [%s]\n", argv[1]);
        if ( ParseWinAmountString( argv[1], &amtData ) == TRUE )
        {
            printf("Amount indexes = [ ");
            for( i = 0; i < amtData.numImages; i++ )
            {
                printf("%d ", amtData.indexes[i]);
            }
            printf("]\n");
        }
        else
        {
            printf("Failed to parse amount.\n");
            return( 1 );
        }
    }
    else
    {
        printf("Usage: %s <Amount String>\n", argv[0]);
        return( 1 );
    }

    if( InitImages() == TRUE )
    {
        imgWheelBg = cairo_image_surface_create_from_png("images/blankwheel.png");

        //Create the background image
        cairo_surface_t *imgResult = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 180, 180);

        //Create the cairo context
        cairo_t *cr = cairo_create(imgResult);

        //Paint empty wheel image
        cairo_set_source_surface(cr, imgWheelBg, 0, 0);
        cairo_paint(cr);

        // At this point the wheel is painted ( blankwheel.png )

        // vvvvvvvvvv THIS PART SEEMS TO BE CAUSING TROUBLES vvvvvvvvvv     

        // With this call the wheel DOES get rotated 90 degress. Confirmed
        // by viewing the resulting PNG file. 
        // HOWEVER, after the Rotate() is called the numbers aren't put on the wheel.
        // if I remove the Rotate() call, the wheel is drawn, not rotated, but the
        // numbers are properly composited over the image.

        //Rotate( cr, imgWheelBg, 90 );

        // ^^^^^^^^^^^ THIS PART SEEMS TO BE CAUSING TROUBLES ^^^^^^^^^^^       

        /* Set drawing begin point in pixels */
        x = 101;
        y = 82;

        /* Draw all characters in win amount string */
        for( i = 0; i < amtData.numImages; i++ )
        {
            cairo_set_source_surface(cr, NumberImages[amtData.indexes[i]].image, x, y);
            cairo_paint(cr);
            x += NumberImages[i].xOffset;
        }

        //Destroy the cairo context and/or flush the destination image
        cairo_surface_flush(imgResult);
        cairo_destroy(cr);

        //And write the results into a new file
        cairo_surface_write_to_png(imgResult, "result.png");

        // Destroy resources
        cairo_surface_destroy(imgResult);
        cairo_surface_destroy(imgWheelBg);
        DestroyNumberImages();
        printf("SUCCESS\n");
    }
    else
    {
        printf("FAILED Init Images\n");
    }

    return 0;
}

РЕДАКТИРОВАНИЕ: конечной целью является создание изображения, подобного этому. программно и анимировать его по мере необходимости в режиме реального времени в приложении GTK.

EDIT: С комментариями Михаила Кожевникова и Ули Шлахтер Мне удалось найти решение, используя этот код.

Большое спасибо!


person Chimera    schedule 28.06.2012    source источник


Ответы (1)


Я думаю, проблема в том, что трансформация применяется ко всему, в том числе и к числовым изображениям, и они из-за этой трансформации прорисовываются где-то за пределами картинки. Я бы посоветовал сохранить матрицу перед применением преобразования и восстанавливается после рисования круга. Или вы хотите, чтобы цифры тоже вращались?..

person Qnan    schedule 28.06.2012
comment
Спасибо Михаил. Да, я бы хотел, чтобы числа вращались вместе с колесом. Таким образом, после каждого поворота и применения чисел у меня появляется новое колесо (фон), которое содержит ранее примененные числа. При следующем повороте фон с колесом и цифрами поворачивается, чтобы я мог добавить больше цифр. Конечный результат будет примерно таким: i.imgur.com/AM9Mo.png Я ценю помощь. - person Chimera; 28.06.2012
comment
Я очень новичок в Каире и 2D-графике в целом. Я вроде понимаю документацию Cairo, но часто не знаю, как реализовать то, что я пытаюсь сделать. Поэтому мне легче понять это, если у меня есть рабочий образец кода. Я с радостью предоставлю вознаграждение любому, кто может помочь мне с образцом кода, чтобы я мог собрать все это воедино и действительно понять, что происходит. - person Chimera; 28.06.2012
comment
В этом случае попробуйте добавить cairo_translate(cr, -90, -90) после cairo_paint в функцию Rotate (строка 146 в вашем коде на pastebin, я полагаю). Это возвращает числовые изображения на холст и удерживает их повернутыми под тем же углом, что и колесо. Чтобы нарисовать больше чисел под другими углами, вам, конечно, понадобится больше поворотов. - person Qnan; 28.06.2012
comment
Спасибо Михаил. Это делает меня намного ближе. - person Chimera; 29.06.2012
comment
Вы должны использовать cairo_save(cr) в начале Rotate() и cairo_save(cr) в конце. Это гарантирует, что cairo_translate() и cairo_rotate() не повлияют на внешний код, и ИМХО лучше, чем возиться с cairo_set_matrix(). - person Uli Schlachter; 29.06.2012
comment
@UliSchlachter в первом комментарии к этому ответу автор заявляет, что хочет, чтобы числа вращались вместе с колесом. - person Qnan; 29.06.2012
comment
@UliSchlachter, хотя у меня есть ощущение, что автор путает вращение чисел и вращение колеса ... поскольку вращение колеса более чем на единицу не имеет большого смысла с точки зрения рендеринга. - person Qnan; 29.06.2012
comment
@UliSchlachter ... и вы, конечно, правы насчет cairo_save и cairo_load. - person Qnan; 29.06.2012
comment
Спасибо вам двоим. Я это очень ценю. Вот мое окончательное решение. Не стесняйтесь просмотреть его (если хотите) и дайте мне знать о любых способах оптимизации или лучшего использования Cairo. pastebin.com/6jkn2sFp - person Chimera; 29.06.2012