画像を拡大・縮小する時の補間処理

 ペイント(ラスタ)形式の画像を縮小する時、元画像からそのままピクセルを取り出すと、線が途切れたりします。間引かれたピクセルの情報が捨てられてしまうのが原因です。このような場合、補間処理を行うことで、画像が荒くなることを防げます。

縮小

単純な縮小

 いま、以下の画像を縦横 1/2 に縮小します。

元の画像(320×240)

 単純に、ピクセルを一つおきに取り出して画像を作ればいいのです。この処理は、SDL2 のレンダラが行います。

#include <stdio.h>
#include <SDL.h>

void draw(SDL_Renderer *renderer) {
    SDL_Texture *bmpTex;
    SDL_Surface *image;

    /* バッファ初期化 */
    SDL_RenderClear(renderer);

    /* 画像読み込み */
    image = SDL_LoadBMP("base.bmp");
    bmpTex = SDL_CreateTextureFromSurface(renderer, image);
    SDL_FreeSurface(image);

    /* 画像をバッファに書き込み */
    SDL_RenderCopy(renderer, bmpTex, NULL, NULL);
    SDL_DestroyTexture(bmpTex);

    /* 表示 */
    SDL_RenderPresent(renderer);
}

int main(int argc, char* argv[]){
    SDL_Window *window;
    SDL_Renderer *renderer;
    SDL_Event event;
    int quit_flg = 1;

    /* 初期化 */
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0) {
        fprintf(stderr, "SDL_Init(): %s\n", SDL_GetError());
        exit(1);
    }
    if (SDL_Init(SDL_INIT_JOYSTICK) >= 0 ) {
        if( SDL_JoystickOpen(0) != NULL )
            SDL_ShowCursor(SDL_DISABLE);
    }

    /* ウィンドウ作成 */
    window = SDL_CreateWindow("TestDraw", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 160, 120, SDL_WINDOW_OPENGL);
    if( window == NULL ) {
        printf("Can not create window\n");
        exit(1);
    }
    renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    if( renderer == NULL ) {
        printf("Can not create renderer\n");
        exit(1);
    }

    draw(renderer);

    /* イベントループ */
    while(quit_flg) {
        while( SDL_PollEvent(&event) ) {
            switch (event.type) {
            case SDL_JOYBUTTONDOWN:
            case SDL_KEYDOWN:
            case SDL_QUIT:
                quit_flg = 0;
                break;
            }
        }
    }
    if (renderer) SDL_DestroyRenderer(renderer);
    if (window) SDL_DestroyWindow(window);

    SDL_Quit();

    return 0;
}
単純な縮小

 文字も読みづらいですし、円も切れています。

補間処理を行う

 補間処理を行います。あまり複雑なことはしたくないので、単純に元の画像にぼかしをかけることにします。ぼかしをかけることで、周りのピクセルの情報を含めることが出来るので、縮小時に間引かれるピクセルの情報が無くなってしまうことを防ぐ効果があります。

 ここでは、周りのピクセルと平均を計算します。

#include <stdio.h>
#include <SDL.h>

Uint32 average_pixel(Uint32 *pixels, int n) {
    int i;
    Uint32 r, g, b, a;

    r = g = b = a = 0;
    for( i = 0; i < n; i++ ) {
        r += (*(pixels + i) >> 24) & 0xff;
        g += (*(pixels + i) >> 16) & 0xff;
        b += (*(pixels + i) >>  8) & 0xff;
        a += *(pixels + i) & 0xff;
    }
    r /= n;
    g /= n;
    b /= n;
    a /= n;

    return (((r << 24) & 0xff000000) | ((g << 16) & 0xff0000) |
            ((b <<  8) & 0xff00) | (a & 0xff));
}

void copy_pixels(SDL_Surface *src, SDL_Surface *dst) {
    Uint32 *s, *d;
    Uint32 p[9];
    int i, j, k, l, m;

    s = src->pixels;
    d = dst->pixels;

    for( i = 0; i < dst->h; i++ ) {
        for( j = 0; j < dst->w; j++ ) {
            m = 0;
            for( k = -1; k < 2; k++ ) {
                if( (i + k) < 0 || (i + k) >= dst->h ) continue;
                for( l = -1; l < 2; l++ ) {
                    if( (j + l) < 0 || (j + l) >= dst->w ) continue;
                    p[m++] = *(s + (i+k) * src->w + j + l);
                }
            }

            *(d + i * dst->w + j) = average_pixel(p, m);
        }
    }
}

void draw(SDL_Renderer *renderer) {
    SDL_Texture *bmpTex;
    SDL_Surface *image, *buf;

    /* バッファ初期化 */
    SDL_RenderClear(renderer);

    /* 画像読み込み */
    image = SDL_LoadBMP("base.bmp");
    if( image->format->BitsPerPixel != 32 ) {
        printf("Not support %d depth image\n",  image->format->BitsPerPixel);
        exit(-1);
    }

    buf = SDL_CreateRGBSurface(0, image->w, image->h, image->format->BitsPerPixel, 0, 0, 0, 0);

    /* ぼかした画像を作成 */
    copy_pixels(image, buf);
    bmpTex = SDL_CreateTextureFromSurface(renderer, buf);
    SDL_FreeSurface(image);
    SDL_FreeSurface(buf);

    /* 画像をバッファに書き込み */
    SDL_RenderCopy(renderer, bmpTex, NULL, NULL);
    SDL_DestroyTexture(bmpTex);

    /* 表示 */
    SDL_RenderPresent(renderer);
}

int main(int argc, char* argv[]){
    SDL_Window *window;
    SDL_Renderer *renderer;
    SDL_Event event;
    int quit_flg = 1;

    /* 初期化 */
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0) {
        fprintf(stderr, "SDL_Init(): %s\n", SDL_GetError());
        exit(1);
    }
    if (SDL_Init(SDL_INIT_JOYSTICK) >= 0 ) {
        if( SDL_JoystickOpen(0) != NULL )
            SDL_ShowCursor(SDL_DISABLE);
    }

    /* ウィンドウ作成 */
    window = SDL_CreateWindow("TestDraw", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 160, 120, SDL_WINDOW_OPENGL);
    if( window == NULL ) {
        printf("Can not create window\n");
        exit(1);
    }
    renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    if( renderer == NULL ) {
        printf("Can not create renderer\n");
        exit(1);
    }

    draw(renderer);

    /* イベントループ */
    while(quit_flg) {
        while( SDL_PollEvent(&event) ) {
            switch (event.type) {
            case SDL_JOYBUTTONDOWN:
            case SDL_KEYDOWN:
            case SDL_QUIT:
                quit_flg = 0;
                break;
            }
        }
    }
    if (renderer) SDL_DestroyRenderer(renderer);
    if (window) SDL_DestroyWindow(window);

    SDL_Quit();

    return 0;
}
補間処理をして縮小

 これで元の形が崩れないように縮小ができました。平均の計算は、加重平均したほうがもう少し良い結果が得られると思います。

拡大

 拡大の場合は、単純に拡大しただけで元の形が崩れるようなことはありません。2 倍に拡大した場合、以下のようになります。

補間無しで拡大

 曲線のギザギザ(ジャギー)が目につきますが、形が崩れることはありません。どうしてもジャギーを緩和したければ、ぼかしをかけますが、縮小のように単純なぼかしでは画像がなまってしまうので、中心ピクセルに大きな重さをもたせた加重平均を使います。

#include <stdio.h>
#include <SDL.h>

Uint32 average_pixel(Uint32 *pixels, int n, int gr[]) {
    int i;
    Uint32 r, g, b, a;
    int m;

    r = g = b = a = m = 0;
    for( i = 0; i < n; i++ ) {
        m += gr[i];
        r += ((*(pixels + i) >> 24) & 0xff) * gr[i];
        g += ((*(pixels + i) >> 16) & 0xff) * gr[i];
        b += ((*(pixels + i) >>  8) & 0xff) * gr[i];
        a += (*(pixels + i) & 0xff) * gr[i];
    }
    r /= m;
    g /= m;
    b /= m;
    a /= m;

    return (((r << 24) & 0xff000000) | ((g << 16) & 0xff0000) |
            ((b <<  8) & 0xff00) | (a & 0xff));
}

void copy_pixels(SDL_Surface *src, SDL_Surface *dst) {
    Uint32 *s, *d;
    Uint32 p[9];
    int g[9] = { 1, 1, 1, 1, 7, 1, 1, 1, 1 };
    int i, j, k, l, m;
    double n;

    s = src->pixels;
    d = dst->pixels;
    n = (double)dst->w / src->w;

    for( i = 0; i < dst->h; i++ ) {
        for( j = 0; j < dst->w; j++ ) {
            m = 0;
            for( k = -1; k < 2; k++ ) {
                if( (i + k) < 0 || (i + k) >= dst->h ) {
                    p[m++] = *(s + (int)(i/n) * src->w + (int)(j/n));
                    continue;
                }
                for( l = -1; l < 2; l++ ) {
                    if( (j + l) < 0 || (j + l) >= dst->w ) {
                        p[m++] = *(s + (int)(i/n) * src->w + (int)(j/n));
                        continue;
                    }
                    p[m++] = *(s + (int)((i+k)/n) * src->w + (int)((j+l)/n));
                }
            }

            *(d + i * dst->w + j) = average_pixel(p, m, g);
        }
    }
}

void draw(SDL_Renderer *renderer) {
    SDL_Texture *bmpTex;
    SDL_Surface *image, *buf;

    /* バッファ初期化 */
    SDL_RenderClear(renderer);

    /* 画像読み込み */
    image = SDL_LoadBMP("base.bmp");
    if( image->format->BitsPerPixel != 32 ) {
        printf("Not support %d depth image\n",  image->format->BitsPerPixel);
        exit(-1);
    }
    buf = SDL_CreateRGBSurface(0, image->w*2, image->h*2, image->format->BitsPerPixel, 0, 0, 0, 0);
    copy_pixels(image, buf);
    bmpTex = SDL_CreateTextureFromSurface(renderer, buf);
    SDL_FreeSurface(image);
    SDL_FreeSurface(buf);

    /* 画像をバッファに書き込み */
    SDL_RenderCopy(renderer, bmpTex, NULL, NULL);
    SDL_DestroyTexture(bmpTex);

    /* 表示 */
    SDL_RenderPresent(renderer);
}

int main(int argc, char* argv[]){
    SDL_Window *window;
    SDL_Renderer *renderer;
    SDL_Event event;
    int quit_flg = 1;

    /* 初期化 */
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0) {
        fprintf(stderr, "SDL_Init(): %s\n", SDL_GetError());
        exit(1);
    }
    if (SDL_Init(SDL_INIT_JOYSTICK) >= 0 ) {
        if( SDL_JoystickOpen(0) != NULL )
            SDL_ShowCursor(SDL_DISABLE);
    }

    /* ウィンドウ作成 */
    window = SDL_CreateWindow("TestDraw", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_OPENGL);
    if( window == NULL ) {
        printf("Can not create window\n");
        exit(1);
    }
    renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    if( renderer == NULL ) {
        printf("Can not create renderer\n");
        exit(1);
    }

    draw(renderer);

   /* イベントループ */
    while(quit_flg) {
        while( SDL_PollEvent(&event) ) {
            switch (event.type) {
            case SDL_JOYBUTTONDOWN:
            case SDL_KEYDOWN:
            case SDL_QUIT:
                quit_flg = 0;
                break;
            }
        }
    }
    if (renderer) SDL_DestroyRenderer(renderer);
    if (window) SDL_DestroyWindow(window);

    SDL_Quit();

    return 0;
}
拡大後にぼかし

 まあ、処理が多くなるのでやらなくてもいいですが、余裕があればやったほうがいい画像が出来上がります。

SDL2

Posted by sirius