blocked_range2dで画像を作ってみた

ousttrue2008-12-23

blocked_range2dという2Dのサンプリングにうってつけのものがあるので使い方を調べてみた。
Tutorialによるとシングルコアでもリニアに走査するよりメモリのキャッシュヒット率が上がるのでよいものだというニュアンスのことが書いてあったがそうなのか。

#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <fstream>
#include "tbb/task_scheduler_init.h"
#include "tbb/blocked_range2d.h"
#include "tbb/parallel_for.h"

struct Circle
{
  float center_x;
  float center_y; 
  float r;
  unsigned char intersect(const int x, const int y)const
  {
    float dx=x-center_x;
    float dy=y-center_y;
    float d=dx*dx+dy*dy;
    float sqr=r*r;
    if(d>sqr){
      return 0;
    }
    return std::sqrt(sqr-d)/r * 255.0f;
  }
};

struct Image
{
  const int w;
  const int h;
  std::vector<unsigned char> rgba32;

  Image(const int width, const int height)
  : w(width), h(height), rgba32(width*height*4)
  {}

  void set(const int x, const int y, 
      const unsigned char r, const unsigned char g, const unsigned char b,
      const unsigned char a)
  {
    unsigned char *p=&rgba32[(y*w+x)*4];
    p[0]=r;
    p[1]=g;
    p[2]=b;
    p[3]=a;
  }

  void DWORD(std::ostream &os, unsigned long rhs)
  {
    unsigned char buf[4];
    buf[0]=rhs & 0xFF;
    buf[1]=(rhs>>8) & 0xFF;
    buf[2]=(rhs>>16) & 0xFF;
    buf[3]=(rhs>>24) & 0xFF;
    os.write((char*)buf, 4);
  }
  void WORD(std::ostream &os, unsigned short rhs)
  {
    unsigned char buf[2];
    buf[0]=rhs & 0xFF;
    buf[1]=(rhs>>8) & 0xFF;
    os.write((char*)buf, 2);
  }
  void LONG(std::ostream &os, long rhs)
  {
    unsigned char buf[4];
    buf[0]=rhs & 0xFF;
    buf[1]=(rhs>>8) & 0xFF;
    buf[2]=(rhs>>16) & 0xFF;
    buf[3]=(rhs>>24) & 0xFF;
    os.write((char*)buf, 4);
  }

  void write(const char *path)
  {
    std::ofstream io(path, std::ios::binary);
    if(!path){
      return;
    }

    // BITMAPFILEHEADER
    io << 'B';
    io << 'M';
    DWORD(io, 54+rgba32.size());
    WORD(io, 0);
    WORD(io, 0);
    DWORD(io, 54);

    // BITMAPINFOHEADER
    DWORD(io, 40);
    LONG(io, w);
    LONG(io, h);
    WORD(io, 1);
    WORD(io, 32);
    DWORD(io, 0);
    DWORD(io, w*h*4);
    LONG(io, 0);
    LONG(io, 0);
    DWORD(io, 0);
    DWORD(io, 0);

    // rawdata
    io.write((const char *)&rgba32[0], rgba32.size());
  }
};

// blocked_range2d
struct RectFunctor
{
  Image &image;
  const Circle &circle;

  RectFunctor(Image &_image, const Circle &_circle)
  : image(_image), circle(_circle)
  {}

  void operator()(const tbb::blocked_range2d<size_t>& r)const
  {
    /* 修正
    tbb::blocked_range2d<size_t>::row_range_type vertical
      =r.rows();
    tbb::blocked_range2d<size_t>::row_range_type horizontal
      =r.cols();
    */
    tbb::blocked_range2d<size_t>::row_range_type horizontal
      =range.rows();
    tbb::blocked_range2d<size_t>::col_range_type vertical
      =range.cols();

    for(size_t y=vertical.begin(); y!=vertical.end(); ++y){
      for(size_t x=horizontal.begin(); x!=horizontal.end(); ++x){
        if(x==horizontal.begin() || x==horizontal.end()
            || y==vertical.begin() || y==vertical.end()){
          image.set(x, y, 0, 0, 255, 0);
        }
        else{
          unsigned char gray_scale=circle.intersect(x, y);
          image.set(x, y, gray_scale, gray_scale, gray_scale, 0);
        }
      }
    }
  }
};

void write_circle_2D(Image &image, const Circle &circle)
{
  tbb::parallel_for(tbb::blocked_range2d<size_t>(0, image.w, 0, image.h),
      RectFunctor(image, circle)
      ,tbb::auto_partitioner()
      );
}

int main(int argc, char **argv)
{
  Image image(150, 150);
  std::fill(image.rgba32.begin(), image.rgba32.end(), 0);

  Circle circle;
  circle.center_x=image.w/2;
  circle.center_y=image.h/2;
  circle.r=std::min(image.w, image.h)/2-5;

  tbb::task_scheduler_init init;
  write_circle_2D(image, circle);

  image.write("tmp.bmp");
  return 0;
}

ファンクタ内のImageへのリファレンスをmutableにする必要があるかと
思ったが必要なかった。
計画ではparallel_reduceの方を使うつもりなのでよいのだけど。