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に飛びデータを取ってくるようにしなければなりません。また、例外処理も行わなければならないでしょう。)