2014/08/28

C++ std::list, std::vector, std::dequeをまとめておく

vector

cplusplus

arrayのように連続ストレージ系のコンテナ。

  • arrayと違って可変長。
  • ランダムアクセス性が高い。
  • endに対してのaddやremoveはlist,dequeに対して効率的

  • 頻繁なallocationを防ぐために余分にメモリ確保される。
    • (actual sizeに対してcapacityを大きく確保するから。ライブラリによってallocationタイミングは分かれるが、一般的いはlogインターバル)
  • end以外へのinsertやremoveはlist,dequeに対して遅い
  • iteratorの定常性というかconsistencyはlistに比べると低い

list

cplusplus

固定処理時間でのinsert、erase(場所によらない)が特徴の双方向リストコンテナ

  • insert, erase, moveが他のコンテナと比較して速い

  • ランダムアクセス性は低い、begin/endとかからたどる必要がある(はず)O(n)

deque

cplusplus

Double-Ended-Queueの略。Queueの一種なのでbeginとendからしか拡張できない。

  • arrayと違って、可変長
  • listと違って、ランダムアクセス性は高い
    • vectorと違って分割されたChunk(+ランダムアクセス用の情報)に分けてデータを保存しているらしい。
  • vectorと違って、endだけでなくbeginへのaddやremoveも効率的

  • vectorと違って、連続的な保存は保証されない(ポインタ+オフセットでアクセスしちゃダメ)
  • beginやend以外へのinsert、removeは遅い
  • iteratorの定常性というかconsistencyはlistに比べると低い

自分用Sublime Text Plugin/設定メモ

Emacsライクな使い方にする為にST3のほうがおすすめな印象

  • SublemacsPro

    • 基本的なキーバインドはこれで
  • IMESupport(Win)

    • Windowsの場合は日本語入力をインライン化する為に必要
  • Alignment

  • Brackethighlighter

  • AutoFileName

  • OrigamiEmacs (ST2の場合のみ、ST3には同様の機能が入ってる)

    • ペインの分割とそのショートカット 参考サイト:http://xoyip.hatenablog.com/entry/2014/03/27/211804
  • Markdown用

    • OmniMarkupPreviewer Ctrl+Alt+Oでデフォルトブラウザでプレビューしてくれるらしいが、 Ubuntu?SublemacsPro?だからか効かななった。でも右クリックからプレビュー表示が可能なので問題なし

    • Trailing Spaces 改行になるスペースx2をハイライトしてくれてグー

    • monokai extended

    • Markdown Extended

  • KeyBinding ST3の時(Macでは不要だった)

    • ctrl+hのキーをleft_deleteに切り替える
 { "keys": ["ctrl+h"], "command": "left_delete" },
 // for markdown
 { "keys": ["shift+enter"], "command": "insert_snippet", "args": {"contents": "  \n"}},
  • Preference追加した項目を晒す
    "theme": "Flatland Dark.sublime-theme",
    "color_scheme": "Packages/Theme - Flatland/Flatland Dark.tmTheme",
    "flatland_square_tabs": false,

    "translate_tabs_to_spaces": true,
    "highlight_line": true,
    "draw_white_space": "all",

2014/03/08

QCustomPlotで表示したグラフをマウスホイールズームやDragシフトする際の範囲を制限する

QCustomPlotでマウスホイールズーム&Dragでシフトで紹介したようにほんの数行加えるだけで、 表示領域を操作できるのだが、0原点だけはズームやシフトしたい場合にも固定したい場合は結構多い。

そういう時はどういう風にするかを紹介する。

ここで書かれている内容だが、
まず、制限したいQCPAxisのrangeChangedシグナルにslotを接続する↓。

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    mpHistogramBars = new QCPBars(ui.histogram->xAxis, ui.histogram->yAxis);
    mpHistogramBars->setPen(QPen(Qt::gray));// 色指定
    mpHistogramBars->setBrush(QBrush(Qt::gray));// 色指定
    ui.histogram->addPlottable(mpHistogramBars);// 追加
    ui.histogram->setInteraction(QCP::iRangeDrag, true);
    ui.histogram->setInteraction(QCP::iRangeZoom, true);
    ui.histogram->axisRect()->setRangeDrag(Qt::Vertical);
    ui.histogram->axisRect()->setRangeZoom(Qt::Vertical);

    // yAxisRangeChanged()はslot
    connect(ui.histogram->yAxis, SIGNAL(rangeChanged(const QCPRange &)), this, SLOT(yAxisRangeChanged(const QCPRange &)));
}


のようにしておいてRangeが変わったら、すなわちズームやシフトが発生したらyAxisRangeChangedが呼ばれるようにしておき、
yAxisRangeChangedの中で以下のようにRangeを再セットしてやる。

void MainWindow::yAxisRangeChanged(const QCPRange &newRange)
{
    ui.histogram->yAxis->setRange(0, ui.histogram->yAxis->range().upper);// y軸の下限を0に固定
}


当然、マイナスの値にはしたくないという制限の場合には以下のようになる。

void MainWindow::yAxisRangeChanged(const QCPRange &newRange)
{
    if(newRange.lower < 0){
        ui.histogram->yAxis->setRange(0, ui.histogram->yAxis->range().upper);
    }
}


常に下限を0にした場合こんな感じになる


2014/03/07

QCustomPlotでマウスホイールズーム&Dragでシフト

あんまり簡単なんで書くのもなんなのだが・・・。

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    mpHistogramBars = new QCPBars(ui.histogram->xAxis, ui.histogram->yAxis);
    mpHistogramBars->setPen(QPen(Qt::gray));// 色指定
    mpHistogramBars->setBrush(QBrush(Qt::gray));// 色指定
    ui.histogram->addPlottable(mpHistogramBars);// 追加
    // 以下を追加する事でマウスホイールズームとDragでシフトができるようになる
    ui.histogram->setInteraction(QCP::iRangeDrag, true);
    ui.histogram->setInteraction(QCP::iRangeZoom, true);

    // 例えばさらに垂直方向(y軸方向)だけズームやシフトすると制限する事も可能
    ui.histogram->axisRect()->setRangeDrag(Qt::Vertical);
    ui.histogram->axisRect()->setRangeZoom(Qt::Vertical);
    
}



これだけでマウスホイールズームとDragでのシフトができるようになる。
こんな感じ

次回ではシフト・ズーム範囲を制限してみる。


2014/02/17

popplerを使ってpdfファイルをQImageに読み込む(おまけにQimageをpdfファイルとして書き出す)

前回でインストールしたpopplerを使ってQtアプリでpdfファイルを読み込む。 今回はpdfといってもページ=画像ファイルなのでわりと簡単。


#include <poppler/qt4/poppler-qt4.h>


openPdfFile(QString fileName){
  Poppler::Document *doc = Poppler::Document::load(fileName);
  if(doc == 0) return;

  for(int pageNum = 0; pageNum < doc->numPages(); pageNum++){
    Poppler::Page *pdfPage = doc->page(pageNum);
    
    QImage image = pdfPage->renderToImage();
    // このimageを使って描画処理等を行う

    // pdfPageは使い終わったらdeleteが必要
    delete pdfPage;
  }
  // docは使い終わったらdeleteが必要
  delete doc;
}




一方でpdf出力に関しては、これもテキストを出力するのでなければQt自体がもっているpdf出力を使えるので
以下のようにすればQImageをページいっぱいに描画したpdfが出力できる。



exportPagesAsPdf(QString fileName){

  // QtはQPrinterをつかってpdfファイル出力ができる
  QPrinter printer(QPrinter::HighResolution);
  printer.setOutputFormat(QPrinter::PdfFormat);
  printer.setPageMargins(0, 0, 0, 0, QPrinter::Point);// margin 0にして縁なしにする
  printer.setOutputFileName(fileName);
  const int dpi = 72;
  printer.setResolution(dpi);
  QPainter painter;

  for(int i = 0; i < mPages.size(); i++){
      QImage image = mPages[i].image();

      // 仮に毎ページサイズが変わってもOKにしてる
      printer.setPaperSize(image.size(), QPrinter::Point);
      if(i == 0){
          painter.begin(&printer);// 最初のページはnewPage()をいれてはいけないので特別処理
      }
      else{
          printer.newPage();// サイズを指定した後にnewPage()を呼ぶとそのサイズのPageが挿入される
      }
      painter.drawImage(0, 0, image);
  }
}




dpiをなぜ72にしたらいいのか実はちょっとわかってないんだが、popplerのdocumentのPage::renderToImage()のdpiのデフォルト引数が72.0だったので、一緒にしたらいいんじゃないかなぁと思ったらうまくいった。

QPrinterのAPIをみるとテキストをセットするようなものはないので、
OCRしたテキストをpdfに埋め込みたい時には多分QPrinterでは無理なんじゃないかな?
きっとHandling PDFにあるようにほかのthird partyライブラリを 使えばできるんだろうけど。今回はまぁこの辺で。


popplerをhomebrewを使ってインストール

最近、ドキュメントスキャナを購入してから細々と自炊して本・マンガ本の省スペース化を図っている。
WindowsはeTritranという素晴らしいソフトがあるが、
Macで同じような事(簡単なトーンカーブ補正、ページ回転等々)をやろうとした時に、
ちょうどいいソフトが見つからなかったので、機能は制限されるがQtを使って自分で作ってたりする → こんな感じePage

今の所zip(jpegのzip化)の解凍はquazipを使って読み込むことができるようになったんだが、
段々欲が出てきてpdfも読み書きしたくなってきた。

ということでQtにpdfを読み書きする方法があるのかと探すと・・どうもなさそう。
詳細はHandling_PDFに書かれているが、
出力する事はQPrinterを使ってできるみたい。でも読む方はQtにはなくて3rd partyのライブラリを使わないといけないみたい。

こんな感じで3つほど紹介されてる。


今回は自炊した漫画本をメインで考えてるので出力はQPrinterを使って、読み込む機能があるpopplerを使う事にする。 Qt wrapperがあってQtとの親和性も高そう。
で、popplerをソースからコンパイルしてもいいんだが、freetype2が必要だったりlibpngやlibopenjpegが必要だったり 結構依存ライブラリがおおい。なので、この際今まで使った事がなかったhomebrewを使って入れてみようかなと。
homebrewとそのインストールついてはパッケージ管理システム Homebrewなど、いい参考サイトがあるのでそちらをみてもらうとして。
コマンド一つでインストールできる所が美しい・・。

 $brew update
 $brew install poppler --with-qt4


とすると、デフォルトでは/usr/local/Cellar/以下にpopplerと依存ファイル(qt4も)インストールしてくれます。 楽だなー。
なので、.proファイルに以下を追加する事でプログラム中でpopplerを使えるようになります。

LIBS += -lpoppler-qt4

INCLUDEPATH += /usr/local/Cellar/poppler/0.24.5/include

DEPTHDPATH += /usr/local/Cellar/poppler/0.24.5/lib


具体的な使い方は次回で。


2014/01/07

QTableViewへ登録するQStandardItemにでユーザ定義型をくるませる

QTableView等のQ*View系のQt Model/View構造で管理されているビューを使用する場合、 もっとも簡単な使い方として、QStandardItemModelへQStandardItemをセットしていく事になる。

  • QStandardItem = Table等に表示される1項目
  • QStandardItemModel = Itemの集合(row/column/parent/childの概念)

この際に、Tableのセルに表示させる文字列(QString)以外にユーザ定義型をQStandardItem::setDataを使って格納することができる。

個人的にはクラスのポインタを各Itemに格納させておいて、TableView(or ListView)でDrag&Dropして順番を入れ替えた後、 入れ替えた順番でクラスのポインタを順にとってくるという事をしたかった。

このサイト(QVariant の使い方)によるとポインタはもたせられなくてvoidにしなさいとあるんだが、実際にはvoidにしてもうまくいかなかったので追加調査

参考にしたのは以下の2つのサイト

今回はポインタだったので不要だったかもしれないが、こちらのサイトによるとユーザ定義型の場合は追加で以下の3つが必要となるよう。

  • QObjectの継承
  • コピーコンストラクタの定義
  • 代入演算子の定義

以下ではMyClassのconstポインタをQStandardItemに格納している。

まず、下準備として以下が必要。

// const MyClass*をQMetaTypeとして使いますよという宣言
Q_DECLARE_METATYPE(const MyClass*);

// 以下のoperator定義はDrag&Dropをしたい場合に必要
// もしやらないと、"QVariant Invalid type to save" のようなエラーが大量にでる
// see. https://qt-project.org/forums/viewthread/22238
QDataStream& operator<<(QDataStream& out, const MyClass* const& rhs)
{
    out.writeRawData(reinterpret_cast<const char*>(&rhs), sizeof(rhs));
    return out;
}
QDataStream& operator >> (QDataStream& in, const MyClass*& rhs)
{
    in.readRawData(reinterpret_cast<char*>(&rhs), sizeof(rhs));
    return in;
}

// const MyClass*をQMetaTypeとして使う前(たとえばコンストラクタ)に以下を書く
MyApp::MyApp(){
    qRegisterMetaTypeStreamOperators<const MyClass*>("const MyClass*");
}

そして、実際に値をセットするときは

void MyApp::setData(QList<MyClass*> myClasses){
    QStandardItemModel* model = qobject_cast<QStandardItemModel*>(myTableView->model());

    foreach(const MyClass* class, myClasses) {
        QStandardItem* item = new QStandardItem(class->name());
        item->setData(QVariant::fromValue(class));
        model->appendRow(item);
    }
}

な感じでセット(myTableView => QTableView*ね)

逆に値を引っ張ってくる時は

QList<MyClass*> MyApp::getData(){
    QStandardItemModel* model = qobject_cast<QStandardItemModel*>(myTableView->model());

    QList<const MyClass*> myClasses;
    const int COLUMN_NO = 0;// setDataでMyClass*を格納したQStandardItemが入っているColumn番号 = 0
    for (int i = 0; i < model->rowCount(); i++) {
        myClasses.push_back(model->item(i, COLUMN_NO)->data().value<const MyClass*>());
    }
    return myClasses;
}

のような感じで取得できる。