スポンサーリンク

角丸長方形を描画

1/4ずつの円弧とブレゼンハムを組み合わせて角丸長方形(rounded rectangle)を描画する。

前回の円描画であえて1/4ずつ描画できるように作ったので、それをブレゼンハムと組み合わせる。

 

draw.hpp (rounded_rectangle)

#pragma once

#include <vector>


struct Image {
  std::vector<unsigned char> img;
  int width;
  int height;
};
void resize(Image* img, int w, int h) {
  img->width = w;
  img->height = h;
  img->img.resize(w * h);
}

//! @brief 画像のピクセルを着色する
//! @param [in,out] img 画像
//! @param [in] x 座標
//! @param [in] y 座標
//! @return なし
void plot(Image& img, int x, int y) {
  img.img[y * img.width + x] = 255;
}


enum class CPOS {
  left, right, top, bottom
};

//! @brief 円の指定した1/4を描画する
//! @param [out] img 画像
//! @param [in] cx 中心座標
//! @param [in] cy 中心座標
//! @param [in] r  半径
//! @param [in] bx 円の四分割したエリアの指定
//! @param [in] by 円の四分割したエリアの指定
//! @return なし
void arc4(Image& img, const int cx, const int cy, const int r, const CPOS bx, const CPOS by) {
  int x, y;//描画位置
  const int rr = r * r;//半径の二乗
  int dx, dy;
  int limx;
  int limy;

  if (bx == CPOS::right && by == CPOS::bottom) {
    // □□
    // □■
    //初期位置
    x = r;
    y = 0;
    dx = -1;
    dy = +1;
    limx = 0;
    limy = r;
  }
  else if (bx == CPOS::left && by == CPOS::bottom) {
    // □□
    // ■□
    x = 0;
    y = r;
    dx = -1;
    dy = -1;
    limx = -r;
    limy = 0;
  }
  else if (bx == CPOS::left && by == CPOS::top) {
    // ■□
    // □□
    x = -r;
    y = 0;
    dx = +1;
    dy = -1;
    limx = 0;
    limy = -r;
  }
  else if (bx == CPOS::right && by == CPOS::top) {
    // □■
    // □□
    x = 0;
    y = -r;
    dx = +1;
    dy = +1;
    limx = r;
    limy = 0;
  }

  //最初の一点
  //plot(img,x + cx, y + cy);

  while ((x != limx) || (y != limy)) {

    /////////////////////////
    // 次に描画するポイントを決定

    //次に描画するのは以下のいずれか。
    int xy1 = pow(x + dx, 2) + pow(y, 2);
    int xy2 = pow(x + dx, 2) + pow(y + dy, 2);
    int xy3 = pow(x, 2) + pow(y + dy, 2);

    // x*x + y*y == r*rなので
    // 上記各xyのうち最もr*rとの差が少ないx*x+y*yを選択
    xy1 = abs(xy1 - rr);
    xy2 = abs(xy2 - rr);
    xy3 = abs(xy3 - rr);

    if (xy1 < xy2) {
      if (xy1 < xy3) {
        // xy1
        x = x + dx;
        y = y;
      }
      else {
        // xy2
        x = x + dx;
        y = y + dy;
      }
    }
    else {
      if (xy2 < xy3) {
        // xy2
        x = x + dx;
        y = y + dy;
      }
      else {
        // xy3
        x = x;
        y = y + dy;
      }
    }

    /////////////////////////
    // 描画
    if (
      (img, x + cx < 0) ||
      (img, x + cx >= img.width) ||
      (img, y + cy < 0) ||
      (img, y + cy >= img.height)
      ) {
      continue;
    }
    plot(img, x + cx, y + cy);


  }

}

      
//! @brief ブレゼンハムの直線描画
//! @param [out] img 画像データ(一次元配列)へのポインタ
//! @param [in] width 画像の幅(画素数)
//! @param [in] height 画像の高さ(画素数)
//! @param [in] sx 線の始点X
//! @param [in] sy 線の始点Y
//! @param [in] ex 線の終点X
//! @param [in] ey 線の終点Y
//! @param [in] color 線の色
void Bresenham(
  Image& img,
  const int sx,
  const int sy,
  const int ex,
  const int ey
) {
  // https://ja.wikipedia.org/wiki/%E3%83%96%E3%83%AC%E3%82%BC%E3%83%B3%E3%83%8F%E3%83%A0%E3%81%AE%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0

  const int dx = std::abs(ex - sx);
  const int dy = std::abs(ey - sy);

  const int nx = (sx < ex) ? 1 : -1;
  const int ny = (sy < ey) ? 1 : -1;

  int err = dx - dy;

  int x = sx;
  int y = sy;
  while (1) {

    plot(img, x, y);

    if (x == ex && y == ey)
      break;

    const int e2 = 2 * err;
    if (e2 > -dy) {
      err = err - dy;
      x += nx;
    }
    if (e2 < dx) {
      err = err + dx;
      y += ny;
    }
  }
}

      

//! @brief 角丸長方形を描画
//! @param [out] img 画像
//! @param [in] sx 長方形始点x
//! @param [in] sy 長方形始点y
//! @param [in] ex 長方形終点x
//! @param [in] ey 長方形終点y
//! @return なし
void rounded_rectangle(
  Image& img,
  const int sx, const int sy,
  const int ex, const int ey, 
  const int r) {

  Bresenham(img, sx + r, sy    , ex - r, sy    );
  Bresenham(img, sx + r, ey    , ex - r, ey    );
  Bresenham(img, sx    , sy + r, sx    , ey - r);
  Bresenham(img, ex    , sy + r, ex    , ey - r);


  arc4(img, sx + r, sy + r, r, CPOS::left, CPOS::top);
  arc4(img, sx + r, ey - r, r, CPOS::left, CPOS::bottom);
  arc4(img, ex - r, sy + r, r, CPOS::right, CPOS::top);
  arc4(img, ex - r, ey - r, r, CPOS::right, CPOS::bottom);
}

呼び出し

#pragma warning(disable:4996)

#include <iostream>

#include "draw.hpp"


//! @brief PPM(RGB各1byte,カラー,テキスト)を書き込む
//! @param [in] fname ファイル名
//! @param [in] vmax 全てのRGBの中の最大値
//! @param [in] width 画像の幅
//! @param [in] height 画像の高さ
//! @param [in] p 画像のメモリへのアドレス
//! @details RGBRGBRGB....のメモリを渡すと、RGBテキストでファイル名fnameで書き込む
void pnmP3_Write(const char* const fname, const int width, const int height, const unsigned char* const p) { // PPM ASCII

  FILE* fp = fopen(fname, "wb");
  fprintf(fp, "P3\n%d %d\n%d\n", width, height, 255);

  size_t k = 0;
  for (size_t i = 0; i < (size_t)height; i++) {
    for (size_t j = 0; j < (size_t)width; j++) {
      fprintf(fp, "%d %d %d ", p[k], p[k], p[k]);
      k++;
    }
    fprintf(fp, "\n");
  }

  fclose(fp);
}

int main()
{
  Image img;
  resize(&img, 250, 200);

  // 中心
  int cx = 250;
  int cy = 250;

  //半径
  int r = 50;


  rounded_rectangle(img,
    50, // sx
    50, // sy
    200,// ex
    150,// ey
    20  // r
  );

  pnmP3_Write("a.pgm", img.width, img.height, img.img.data());


}

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)


この記事のトラックバックURL: