はじめに (対象読者・この記事でわかること)

この記事は、Cairoという2Dグラフィックスライブラリを使ってPNG画像を90°、180°、270°回転させたいC言語プログラマー向けの記事です。
画像を回転させる際に「なぜ上下反転してしまうのか」「なぜ端が切れるのか」「アンチエイリアスが効かないのはなぜか」といった悩みを解決する手順を、実装例と共に解説します。
記事を読み終えると、Cairoの座標系と行列変換の仕組みを理解し、任意の角度へ回転した画像を高品質に保存できるようになります。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。 - C言語の基礎(ポインタ、構造体、動的メモリ) - Cairoの基本的な描画フロー(cairo_create / cairo_destroy) - PNGファイルの読み書き(libpngの基礎知識があれば尚良)

Cairoで画像回転が面倒な理由

Cairoは「ベクター指向」のライブラリです。画像を扱うときも「表紙に貼り付ける」ような発想で、元画像をそのまま回転・移動・スケールしてから「表面に転写」します。そのため、

  • 回転行列を掛けたあとの「余白」が透明になる
  • ピクセル単位で「どこに移るか」を自分で計算する必要がある
  • 90°回転は「縦横サイズの入れ替え」と「原点移動」を同時に考えなければならない

といった落とし穴が潜んでいます。本記事では、これらを回避するための「確実な手順」をコードベースで解説します。

ステップバイステップで実装する

Step 1: 画像を読み込み、新しいサーフェスを用意する

まず、読み込んだPNGのサイズを取得し、回転後のサイズを計算します。90°または270°回転の場合は縦横が入れ替わることに注意してください。

C
#include <cairo.h> #include <math.h> typedef struct { cairo_surface_t *surf; int width; int height; } Image; Image load_png(const char *path) { Image img = {0}; img.surf = cairo_image_surface_create_from_png(path); img.width = cairo_image_surface_get_width(img.surf); img.height = cairo_image_surface_get_height(img.surf); return img; } cairo_surface_t *create_target_surface(int width, int height) { return cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); }

Step 2: 回転行列を適用して描画

Cairoの座標系は「左上原点+Y軸下向き」です。
90°回転を正しく行うには、以下の3ステップが必要です。

  1. 原点を画像の中心へ移動(translate)
  2. 回転行列を適用(rotate)
  3. 画像を描画(元画像の中心が新原点に来るように調整)
C
void rotate_blit(cairo_t *cr, cairo_surface_t *src, int src_w, int src_h, double angle) { /* 中心へ移動 */ cairo_translate(cr, src_w/2.0, src_h/2.0); /* 回転 */ cairo_rotate(cr, angle * M_PI / 180.0); /* 元画像の中心が原点に来るように負の方向へ移動 */ cairo_translate(cr, -src_w/2.0, -src_h/2.0); /* 描画 */ cairo_set_source_surface(cr, src, 0, 0); cairo_paint(cr); }

Step 3: 90°、180°、270°用のラッパーを用意

回転角度によって出力サイズが変わるため、ラッパー関数で吸収します。

C
int save_rotated_png(const char *in_path, const char *out_path, int angle) { Image src = load_png(in_path); if (cairo_surface_status(src.surf) != CAIRO_STATUS_SUCCESS) return -1; int dst_w = src.width; int dst_h = src.height; if (angle == 90 || angle == 270) SWAP(dst_w, dst_h); cairo_surface_t *dst_surf = create_target_surface(dst_w, dst_h); cairo_t *cr = cairo_create(dst_surf); switch (angle) { case 90: rotate_blit(cr, src.surf, src.height, src.width, 90); break; case 180: rotate_blit(cr, src.surf, src.width, src.height, 180); break; case 270: rotate_blit(cr, src.surf, src.height, src.width, 270); break; default: cairo_set_source_surface(cr, src.surf, 0, 0); break; } cairo_surface_write_to_png(dst_surf, out_path); cairo_destroy(cr); cairo_surface_destroy(dst_surf); cairo_surface_destroy(src.surf); return 0; }

ハマりどころと対策

現象 原因 対策
画像が上下反転 デフォルト座標系がY軸下向き 回転行列適用前に cairo_translate(cr, 0, height); cairo_scale(cr, 1, -1); でY軸反転
端が切れる サーフェスサイズが回転後の最大幅をカバーしていない 90°回転時は縦横サイズを入れ替えてサーフェスを確保
画像がぼやける 元サーフェスに cairo_pattern_set_filter(..., CAIRO_FILTER_BILINEAR) を適用していない cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BEST)

解決策

上記のラッパー関数を使うと、90°、180°、270°の回転が1行で完結します。
save_rotated_png("input.png", "out90.png", 90);
save_rotated_png("input.png", "out180.png", 180);
save_rotated_png("input.png", "out270.png", 270);

まとめ

本記事では、CairoでPNG画像を90°、180°、270°回転して保存する手順を解説しました。

  • Cairoの座標系と行列変換の仕組み
  • 回転後の画像サイズ計算とサーフェス確保
  • エッジケアとアンチエイリアスの設定

これらを踏まえることで、高品質かつ確実に画像を回転させることができます。
次回は「任意角度への回転と余白の自動トリミング」「Cairo + libpngで高速化」について掘り下げていきます。

参考資料