2011年12月29日木曜日

( Qt C++ )QPrinter、QPrintDialogを使った画像の印刷


はい、それではQPrinter、QPrintDialogを使った画像の印刷をやっていきます。
サンプルはC++ GUI Programming with Qt4の191ページのコードを少し変更して使います。
そして、いつものようにQtCreaterなどでMainWindowなどのGUI部品を定義済みであるとします。(QtCreaterなどの使い方は ”Qtをはじめよう" を見てください。)
ではコードを

void MainWindow::on_pushButton_clicked()//ボタンクリックスロット
{
    QImage image("//home//ubuntu001//thumbnail.jpg");//印刷するイメージ
    QPrinter printer(QPrinter::ScreenResolution);//プリンター描画のためのペイントデバイス
    QPrintDialog printDialog(&printer, this);//印刷するかどうかのダイアログ表示
  
    if(printDialog.exec() == QDialog::Accepted)//OKなら印刷
   {
        QPainter painter(&printer);
        QRect rect = painter.viewport();
        QSize size = image.size();
        size.scale(rect.size(), Qt::KeepAspectRatio);//縦横比維持

        painter.setViewport(rect.x(), rect.y(), size.width(), size.height());
        painter.setWindow(image.rect());
        painter.drawImage(0, 0, image);
    }
}

はい、簡単ですね。まず印刷用のイメージのを作成します。その後プリンター描画のためのペイントデバイスとなるQPrinterを作成し、QPrintDialogにセットし印刷するかどうかの確認ダイアログを表示しています。QPrinter::ScreenResolutionなどの値はリファレンスを参照してください。(リファレンス

QDialog::Accepted(つまりOKってこと)ならば印刷を開始します。
描画のためQPainterに先ほどのprinter(QPrinter)をセットします。そして、painterのビューポートと画像のサイズを取得し、ビューポート、ウィンドウを変更し印刷しています。
(ちなみにビューポートとは物理座標矩形領域、ウィンドウは論理座標矩形領域です。)


実行すると以下のようになります。(Printで実際にプリントされます。)



以上です。

雑記

今年も残すところあとわずかとなりました。今年はホントくそでしたね。津波はくるわ原発は爆発するは…自然のクソ野郎和の心はどういたしましたか?と言ってやりたいですね。
でもまぁ今年は私にとってはいい年でした。踏ん切りがついた年でしたしね。
来年は私にとって勝負の年です。忙しくなるのでブログの更新は少し遅くなるかもしれません。しかしご安心を。頻度は減ってもQtの基本的tipsなどは最後までやります。なぜなら圧倒的に日本語の情報量が少ないからです。
どこの日本語入門サイトを見ても情報が少なく、本家日本語サイトは情報が濃いが更新が遅い。英語のサンプルは豊富にあるものの、ある程度Qtがわかっていることが前提であるものばかりでこれでは初心者が入れない。これではプラットホームを選ばないという利点があるのにもかかわらず、英語を流れるように読めない平凡人のような人は開発の速度が上がりません。 これでは広まるはずがありません。広まらないということは更に情報が減っていくと…まぁ負のスパイラルに陥るわけですね。そんなことは私が許しません!(プラットホーム別にプログラムを組むのはもうそろそろ淘汰されるべきです。)

まぁとにかく少しでも広がるようにこれからも私はがんばってQt記事を書いていきます。

なお、一月よりブログのタイトルを変更します。その名も

タンスの引き出し

です。我ながらよくこんな才能にあふれたタイトルつけたもんだと感心します。
とりあえず皆さんよいお年を。

2011年12月28日水曜日

( Qt C++ )QImageReaderを使ったサムネイル作成


はい、それではQImageReaderを使ったサムネイル作成をやっていきます。
これは前回のQImageのscaledを使ったものより速いです。(私の環境では30%ぐらい高速)
少し手間がかかりますが、速さ重視ならこちらを使ったほうがいいでしょう。
サンプルは独自のものを使います。
そして、いつものようにQtCreaterなどでMainWindowなどのGUI部品を定義済みであるとします。(QtCreaterなどの使い方は ”Qtをはじめよう" を見てください。)
ではコードを


(mainwindow.h)
namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT//マクロ
   
public:
    explicit MainWindow(QWidget *parent = 0);//コンストラクタ
    ~MainWindow();//デストラクタ

protected:
    void paintEvent(QPaintEvent *);//←ここ重要!ペイントイベント
  
private:
    Ui::MainWindow *ui;//uiにはGUI部品が記述
};

(mainwindow.cpp)
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)//uiにはGUI部品に関する記述
{
    ui->setupUi(this);//uiのGUI部品の初期化
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::paintEvent(QPaintEvent *)//←重要!ペイントイベント
{
    QImageReader imageReader("//home//ubuntu001//sampleImage2.png");
    int width = imageReader.size().width();//元画像の幅
    int height = imageReader.size().height();//元画像の高さ

    imageReader.setScaledSize(QSize(width/2, height/2));//サムネイル作成
    QImage thumbnail = imageReader.read();//QImageへ読み込み

    QPainter widgetPainter(this);
    widgetPainter.drawImage(50, 50, thumbnail);//サムネイル描画
    widgetPainter.drawImage(150, 50, QImage("//home//ubuntu001//sampleImage2.png"));/*比較のため元画像を描画*/
}

はい簡単ですね。理解に重要なのはpaintEventだけですので他は無視してしまっても構いません。
まずQImageReaderの実体を作成します。そして幅、高さを取り出し、setScaledSizeを呼び出して半分の大きさのサムネイルを作成します。(リファレンス
その後、作成したものをQImageに読込み、そのサムネイルと元画像をウィジットへ描画しています。

実行すると以下のようになります。

 
以上です。


※この方法はQImageのscaledよりは速いです。しかし、最速とはいえないものです。さらに高速化を図る必要があるなら他のライブラリを使うことや独自のアルゴリズムを使うことを検討してください。
また、jpeg, tiffなど画像によっては最初からサムネイルが埋め込まれている場合があります。速度と効率を優先するなら埋め込みサムネイルを抽出することを検討してください。
例えば以下のコードでjpeg,tiffからサムネイルがあるならば抽出が可能です。

 (windows7の標準で入ってる壁紙用画像(jpg)なんかで多分抽出できると思います。)
QByteArray MainWindow::Thumbnail(QString filename)
{
    QByteArray thumbnail;
    char marker[1];
    char startTag1[1] = {0xFF};
    char startTag2[1] = {0xD8};
    char endTag1[1] = {0xFF};
    char endTag2[1] = {0xD9};
    int length = 0;
    bool existTunmbnail = false;

    QFile *tempfile = new QFile(filename);
    tempfile->open(QIODevice::ReadOnly);

    QDataStream in(tempfile);
    tempfile->seek(tempfile->pos() + 2);
    while (!in.atEnd())
    {
        in.readRawData(marker,1);
        if(memcmp(marker, startTag1,1) == 0)
        {
            in.readRawData(marker,1);
            if(memcmp(marker, startTag2, 1) == 0)
            {
                length = 2;
                existTunmbnail = true;
                break;
            }
        }
    }

    if(existTunmbnail)
    {
        while (!in.atEnd())
        {
            in.readRawData(marker,1);
            length += 1;
            if(memcmp(marker, endTag1, 1) == 0)
            {
                in.readRawData(marker, 1);
                length += 1;
                if(memcmp(marker, endTag2, 1) == 0)
                {
                    tempfile->seek(tempfile->pos() - length);
                    thumbnail = tempfile->read(length);
                    break;
                }
            }
        }
    }

    return thumbnail;
}
この関数を使うには

QByteArray thumb = MainWindow::Thumbnail("//home//ubuntu001//e.tif");
QFile file("//home//ubuntu001//thumbnail.jpg");//新しい空ファイル作成
file.open(QIODevice::WriteOnly);//書込み専用
file.write(thumb);//保存

のようにします。
(当たり前ですがこの関数は練習用のサンプルです。めんどくさいのでタグ解析もせず、サムネのサイズも取得せず、ただ単にサムネ画像をとりだしています。しっかり作るならExif解析、TIFFヘッダの解析をきちっと行い、ピンポイントでタグやらIFDに飛びデータを取ってくるようにしなければなりません。また、例外処理も行わなければならないでしょう。)