AGG (Anti-Grain Geometry) には PNG ファイルを読み書きする機能がないので、その部分は Cairo を使用。

C++によるサンプルコード。


#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <iostream>
#include <string>
#include <cairo/cairo.h>
#include <agg_rendering_buffer.h>
#include <agg_rasterizer_scanline_aa.h>
#include <agg_ellipse.h>
#include <agg_trans_affine.h>
#include <agg_conv_transform.h>
#include <agg_span_image_filter_rgb.h>
#include <agg_span_image_filter_rgba.h>
#include <agg_span_image_filter_gray.h>
#include <agg_pixfmt_rgba.h>
#include <agg_scanline_u.h>
#include <agg_scanline_p.h>
#include <agg_renderer_scanline.h>
#include <agg_span_allocator.h>
#include <agg_span_interpolator_linear.h>
#include <agg_image_accessors.h>
#include <agg_path_storage.h>
#include <agg_conv_stroke.h>
 
typedef agg::pixfmt_rgba32 pixfmt;
//typedef agg::pixfmt_rgba32_pre pixfmt;
 
static agg::rendering_buffer* create_rendering_buffer(cairo_surface_t& src){
  unsigned char* d = cairo_image_surface_get_data(&src);
  int w = cairo_image_surface_get_width(&src);
  int h = cairo_image_surface_get_height(&src);
  int s = cairo_image_surface_get_stride(&src);
  agg::rendering_buffer* rb = new agg::rendering_buffer();
  rb->attach(d, w, h, s);
  return rb;
}
 
static void draw_background_image(agg::renderer_base<pixfmt>& rbase, double scale){
 
  agg::renderer_scanline_aa_solid<agg::renderer_base<pixfmt> > rs(rbase);
 
  agg::trans_affine mtx;
  mtx *= agg::trans_affine_scaling(scale, scale);
 
  // fill polygon
  {
    agg::path_storage path;
    path.move_to(200, 50);
    path.line_to(100, 200);
    path.line_to(100, 250);
    path.line_to(150, 200);
    path.close_polygon();
    agg::rasterizer_scanline_aa<> ras;
    agg::scanline_p8 sl;
    // affine transform
    agg::conv_transform<agg::path_storage, agg::trans_affine> trans(path, mtx);
    ras.add_path(trans);
    rs.color(agg::rgba8(0,255,0,100));
    agg::render_scanlines(ras, sl, rs);
  }
 
  // draw polygon
  {
    agg::path_storage path;
    path.move_to(150, 50);
    path.line_to(150, 200);
    path.line_to(300, 150);
    path.close_polygon();
    agg::conv_stroke<agg::path_storage> stroke(path);
    stroke.width(5.0);
    stroke.line_cap(agg::butt_cap);
    stroke.line_join(agg::miter_join);
    stroke.inner_join(agg::inner_miter);
    stroke.miter_limit(4.0);
    agg::rasterizer_scanline_aa<> ras;
    agg::scanline_p8 sl;
    // affine transform
    agg::conv_transform<agg::conv_stroke<agg::path_storage>, agg::trans_affine> trans(stroke, mtx);
    ras.add_path(trans);
    rs.color(agg::rgba8(0,0,255,100));
    agg::render_scanlines(ras, sl, rs);
  }
 
  // draw polyline
  {
    agg::path_storage path;
    path.move_to(100, 100);
    path.line_to(200, 100);
    path.line_to(200, 200);
    agg::conv_stroke<agg::path_storage> stroke(path);
    stroke.width(10.0);
    stroke.line_cap(agg::round_cap);
    stroke.line_join(agg::round_join);
    stroke.inner_join(agg::inner_round);
    agg::rasterizer_scanline_aa<> ras;
    agg::scanline_p8 sl;
    // affine transform
    agg::conv_transform<agg::conv_stroke<agg::path_storage>, agg::trans_affine> trans(stroke, mtx);
    ras.add_path(trans);
    rs.color(agg::rgba8(255,0,0,100));
    agg::render_scanlines(ras, sl, rs);
  }
}
 
static void draw_image_bilinear(agg::renderer_base<pixfmt>& rbase, agg::rendering_buffer& image, double dx, double dy, double scale){
 
  pixfmt pixf_img(image);
 
  agg::trans_affine src_mtx;
  src_mtx *= agg::trans_affine_scaling(scale);
  src_mtx *= agg::trans_affine_translation(dx, dy);
 
  agg::trans_affine img_mtx;
  img_mtx *= agg::trans_affine_scaling(scale);
  img_mtx *= agg::trans_affine_translation(dx, dy);
  img_mtx.invert();
 
  agg::span_allocator<agg::rgba8> sa;
 
  typedef agg::span_interpolator_linear<> interpolator_type;
  interpolator_type interpolator(img_mtx);
 
  typedef agg::span_image_filter_rgba_bilinear_clip<pixfmt, interpolator_type> span_gen_type;
  span_gen_type sg(pixf_img, agg::rgba(1.0, 1.0, 1.0, 0.0), interpolator);
 
  agg::rasterizer_scanline_aa<> ras;
  agg::scanline_u8 sl;
  
  agg::path_storage ps;
  ps.move_to(0, 0);
  ps.line_to(image.width(), 0);
  ps.line_to(image.width(), image.height());
  ps.line_to(0, image.height());
  ps.close_polygon();
 
  agg::conv_transform<agg::path_storage> tr(ps, src_mtx);
  ras.add_path(tr);
 
  agg::render_scanlines_aa(ras, sl, rbase, sa, sg);
}
 
static void draw_image_nearest_neighbor(agg::renderer_base<pixfmt>& rbase, agg::rendering_buffer& image, double dx, double dy, double scale){
 
  pixfmt pixf_img(image);
 
  agg::trans_affine src_mtx;
  src_mtx *= agg::trans_affine_scaling(scale);
  src_mtx *= agg::trans_affine_translation(dx, dy);
 
  agg::trans_affine img_mtx;
  img_mtx *= agg::trans_affine_scaling(scale);
  img_mtx *= agg::trans_affine_translation(dx, dy);
  img_mtx.invert();
 
  agg::span_allocator<agg::rgba8> sa;
 
  typedef agg::span_interpolator_linear<> interpolator_type;
  interpolator_type interpolator(img_mtx);
 
  typedef agg::image_accessor_clip<pixfmt> img_src_type;
  img_src_type img_src(pixf_img, agg::rgba(1.0, 1.0, 1.0, 0.0));
 
  typedef agg::span_image_filter_rgba_nn<img_src_type, interpolator_type> span_gen_type;
  span_gen_type sg(img_src, interpolator);
 
  agg::rasterizer_scanline_aa<> ras;
  agg::scanline_u8 sl;
  
  agg::path_storage ps;
  ps.move_to(0, 0);
  ps.line_to(image.width(), 0);
  ps.line_to(image.width(), image.height());
  ps.line_to(0, image.height());
  ps.close_polygon();
 
  agg::conv_transform<agg::path_storage> tr(ps, src_mtx);
  ras.add_path(tr);
 
  agg::render_scanlines_aa(ras, sl, rbase, sa, sg);
}
 
static void draw_images(std::string outfile, void (*draw_image_func)(agg::renderer_base<pixfmt>&, agg::rendering_buffer&, double, double, double)){
 
  int width = 600;
  int height = 400;
  int bytes_per_pixel = 4;
 
  // canvas
  cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
  unsigned char* data = cairo_image_surface_get_data(surface);
  agg::rendering_buffer rbuf;
  rbuf.attach(data, width, height, width * bytes_per_pixel);
  pixfmt pixf(rbuf);
  agg::renderer_base<pixfmt> rbase(pixf);
  rbase.clear(agg::rgba8(255, 255, 255, 255));
 
  // image1: load png image
  std::string image1_path = "input1.png";
  cairo_surface_t* image1_surface = cairo_image_surface_create_from_png(image1_path.c_str());
  agg::rendering_buffer* rbuf_img1 = create_rendering_buffer(*image1_surface);
  
  // image2: load png image
  std::string image2_path = "input2.png";
  cairo_surface_t* image2_surface = cairo_image_surface_create_from_png(image2_path.c_str());
  agg::rendering_buffer* rbuf_img2 = create_rendering_buffer(*image2_surface);
 
  // background image
  draw_background_image(rbase, 1.0);
  draw_background_image(rbase, 2.0);
 
  // drawing images
  draw_image_func(rbase, *rbuf_img1,  10,  20, 1.0);
  draw_image_func(rbase, *rbuf_img2,  30,  40, 1.0);
  draw_image_func(rbase, *rbuf_img1,  50,  60, 1.5);
  draw_image_func(rbase, *rbuf_img2,  70,  80, 1.5);
  draw_image_func(rbase, *rbuf_img1,  90, 100, 2.0);
  draw_image_func(rbase, *rbuf_img2, 110, 120, 2.0);
 
  // save image
  cairo_status_t status = cairo_surface_write_to_png(surface, outfile.c_str());
  
  // delete cairo surface
  cairo_surface_destroy(surface);
  cairo_surface_destroy(image1_surface);
  cairo_surface_destroy(image2_surface);
}
 
int main(void){
  draw_images("output_bilinear.png", draw_image_bilinear);
  draw_images("output_nearest_neighbor.png", draw_image_nearest_neighbor);
  return 0;
}

コンパイルと実行は Mac OS X Lion 上にて、以下のシェルスクリプトでやっている。 image_scaling.cpp がサンプルコードのファイル。


#!/bin/bash
 
rm ./a.out
rm ./output_bilinear.png
rm ./output_nearest_neighbor.png
 
g++ -I/usr/local/include/agg2 -I/usr/local/Cellar/cairo/1.10.2/include -L/usr/local/lib -L/usr/local/Cellar/cairo/1.10.2/lib -lagg -lcairo ./image_scaling.cpp
 
chmod 744 ./a.out
./a.out

実行環境は Mac OS X Lion + AGG 2.5 + cairo 1.10.2

重ねる入力画像: input1.png
重ねる入力画像: input1.png

重ねる入力画像: input2.png
重ねる入力画像: input2.png
この画像は [ヅ] Javaで透過度(アルファ値)のあるPNGファイルを出力する で作成した「背景が new Color(255, 255, 255, 0), // white (transparent)」の画像。

これらの画像を拡大して重ねる。

バイリニア補間で出力した画像: output_bilinear.png
バイリニア補間で出力した画像: output_bilinear.png

ニアレストネイバー補間で出力した画像: output_nearest_neighbor.png
ニアレストネイバー補間で出力した画像: output_nearest_neighbor.png

ちょっと黒っぽくなっているのはなぜなのか。。。

もしかして Cairo で PNG ファイルを読み込んでいるのが原因なのかな。
Cairo は内部で Pre-multiplied alpha (乗算済みアルファ) という形式でデータを持っているみたい。

CAIRO_FORMAT_ARGB32

each pixel is a 32-bit quantity, with alpha in the upper 8 bits, then red, then green, then blue. The 32-bit quantities are stored native-endian. Pre-multiplied alpha is used. (That is, 50% transparent red is 0x80800000, not 0x80ff0000.)

Image Surfaces
プリマルチプライ済みアルファは、ソースカラーを表現するのに使用する用語であり、成分にアルファ値がすでに乗じてあります。プリマルチプライ処理は、各色成分の余分な乗算操作を排除することで画像のレンダリングをスピードアップします。たとえば、RGB 色空間では、プリマルチプライ済みアルファを使用して画像をレンダリングすることで、画像の各ピクセルに対する 3 つの乗算操作(赤×アルファ、緑×アルファ、および青×アルファ)を排除します。
Core Image Programming Guide:Color Components and Premultiplied Alpha

試しに、 agg::pixfmt_rgba32 で処理していたところを agg::pixfmt_rgba32_pre にしてみた。

具体的にはこの typedef を


typedef agg::pixfmt_rgba32 pixfmt;

こんな感じに。


typedef agg::pixfmt_rgba32_pre pixfmt;

処理してみたら、ニアレストネイバー補間のほうはけっこういい感じに見える。

バイリニア補間で出力した画像: output_bilinear.png
バイリニア補間で出力した画像: output_bilinear.png

ニアレストネイバー補間で出力した画像: output_nearest_neighbor.png
ニアレストネイバー補間で出力した画像: output_nearest_neighbor.png

Ref.

tags: agg cairo

Posted by NI-Lab. (@nilab)