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
重ねる入力画像: input2.png
この画像は [ヅ] Javaで透過度(アルファ値)のあるPNGファイルを出力する で作成した「背景が new Color(255, 255, 255, 0), // white (transparent)」の画像。
これらの画像を拡大して重ねる。
バイリニア補間で出力した画像: output_bilinear.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_nearest_neighbor.png
Ref.
- Anti-Grain Geometry -
- Anti-Grain Geometry - AGG (libagg): agg::span_image_filter_rgba_bilinear_clip< Source, Interpolator > Class Template Reference
- Anti-Grain Geometry - AGG (libagg): agg::span_image_filter_rgba_nn< Source, Interpolator > Class Template Reference
- Anti-Grain Geometry - Demo Examples
- Anti-Grain Geometry - image1.cpp
- Anti-Grain Geometry - image_transforms.cpp
- cairographics.org
- [ヅ] Cairo で PNG 画像を読み込んで拡大して agg::renderer_base#blend_from で画像を重ねる
tags: agg cairo
Posted by NI-Lab. (@nilab)