2014年3月14日金曜日

Qtで画像圧縮

ちょっと所用があって、Qtを使った画像圧縮ソフトを作った。

倍率は0から最大99倍まで、不必要に大きなサイズにすると処理が重くなるので注意、
変換後の画像形式はjpegのみ。

ubuntuとwindowsで動作確認

以下コード C++ソースのみ 全ファイルはここ

mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QFileDialog>
#include <QFileInfo>
#include <QDebug>
#include <QPainter>
#include <QRect>


//範囲for用の  添字イテレータ intを返す
class CountIterator
{
public:
    constexpr CountIterator(int set):index(set){;}
   //デリファレンス
    int operator *() const
    {
        const int out=index;
       //index++;
        return out;
    }
    //非一致判定
    bool operator !=(CountIterator set) const{
        if(index!=set.Index()){
            return true;
        }else{
            return false;
        }
    }
    //インクリメント
    void operator ++()
    {
        index++;
    }
    //比較用関数
    int Index() const {
        return index;
    }
private:
    int index;
};

//範囲for用のカウンタ
class SmartCountor
{
public:
    constexpr SmartCountor(int size):index(0),countSize(size){;}
    constexpr SmartCountor(int pos,int length):index(pos),countSize(pos+length){}
    constexpr CountIterator begin() const {return CountIterator(index);}
    constexpr CountIterator end() const {return CountIterator(countSize);}
private:
    int index;
    int countSize;
};


namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private slots:

    void on_ImageFileOpen_triggered();

    void on_ClearButton_clicked();

    void on_SetSaveFolderButton_clicked();

    void on_DoConvertButton_clicked();

    void on_AddImageButton_clicked();

    void on_RemoveIMageButton_clicked();

private:
    Ui::MainWindow *ui;
    QStringList CompressImageNames;
    //QString SaveDirName;
    QString imageSelectDir;
    QString saveFolderName;
    void updateListWidget();
};

#endif // MAINWINDOW_H

mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    setWindowTitle("画像圧縮");
}

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

//ファイルオープン
void MainWindow::on_ImageFileOpen_triggered()
{
    //QString dir;
    //ファイル名を取得
    QStringList strlist=QFileDialog::getOpenFileNames(this,QString("画像選択"),
                                                      imageSelectDir,
                                                      tr("Images (*.png *.xpm *.jpg)"));
    if(strlist.isEmpty()==false){
        imageSelectDir=QFileInfo(strlist[0]).absolutePath();
        CompressImageNames+=strlist;
        updateListWidget();
    }
}

//ファイルリストの削除
void MainWindow::on_ClearButton_clicked()
{

    //ファイル名をクリア
    CompressImageNames.clear();
    //表示をクリア
    ui->listWidget->clear();
}

//保存フォルダ選択
void MainWindow::on_SetSaveFolderButton_clicked()
{
    saveFolderName=QFileDialog::getExistingDirectory(this,"画像ファイルを保存するフォルダを選択");
    ui->lineEdit->setText(saveFolderName);
}

//変換
void MainWindow::on_DoConvertButton_clicked()
{
    //ファイルの変換
    //確認
    if(saveFolderName.isEmpty()==true){
        ui->statusBar->showMessage("保存先のフォルダが設定されていません",5000);
        return;
    }
    if(CompressImageNames.isEmpty()==true){
        ui->statusBar->showMessage("変換する画像データが設定されていません",5000);
        return;
    }
    //変換処理
    auto ConvertImage=[&](QString saveDirName,QString ImageName,const double resize,const int quality){
        //ファイルの読み込み
        QImage comimage(ImageName);
        if(comimage.isNull()==true){
            //存在しない場合
            qDebug() << "image can not open";
            return;
        }
        const QString filename="comp_"+QFileInfo(ImageName).baseName()+".jpg";
        ui->statusBar->showMessage(QString(QFileInfo(ImageName).fileName()+"  "+filename+" に変換中"),-1);
        QImage saveimage(static_cast<int>(comimage.width()*resize),
                         (comimage.height()*resize),QImage::Format_ARGB32_Premultiplied);
        saveimage.fill(Qt::transparent);
        QPainter painter(&saveimage);
        painter.drawImage(QRect(0,0,saveimage.width(),saveimage.height()),comimage);
        QString savename=saveDirName+"/"+filename;
        saveimage.save(savename,"jpg",quality);
        return;
    };

    if(CompressImageNames.isEmpty()==false){
        const double resize=ui->SetResizeFactor->value();
        const int quality=ui->SetQualityBox->value();
        for(const int i:SmartCountor(CompressImageNames.count())){
            ConvertImage(saveFolderName,CompressImageNames[i],resize,quality);
        }
        ui->statusBar->showMessage("画像の変換が完了しました",5000);
    }

}

void MainWindow::on_AddImageButton_clicked()
{
    on_ImageFileOpen_triggered();
}

void MainWindow::on_RemoveIMageButton_clicked()
{
    const int index=ui->listWidget->currentIndex().column();
    CompressImageNames.removeAt(index);
    updateListWidget();
}

void MainWindow::updateListWidget()
{
    //表示のアップデート
    ui->listWidget->clear();
    QStringList filenames;
    filenames.reserve(CompressImageNames.count());
    for(auto &filename:CompressImageNames){
        filenames.append(QFileInfo(filename).fileName());
    }
    ui->listWidget->addItems(filenames);
}






2014年1月13日月曜日

C++11のrange based forで添字を使えるようにしてみた

ということで、新年始まって2週間近く立ちましたが、今更ながら、今年最初の記事を書くことにします。

C++の範囲for便利ですよね。

   QVector<QVector<int>> array{{0,1},{2,3,4},{5,6,7,8}};
   //2次元の動的配列の全要素に安全にアクセス
   for(auto i:array){
       for(auto j:i){
           qDebug() << j;
       }
   }

もう、i++とj++を書き間違えて泣くことは有ない訳です、

でも、ちょ〜っと、気が聞きかない所があります。
イテレータの実装によりますが、基本的にアドレス演算しかしないので、
今何回めのループなのか知りたい場合、範囲forは使えません

   QVector<int> array{0,1,2,3,4,5,6,7,8};
   QVector<int> array2(array.count());
   for(auto elem:array){
       //何回目?
       array2[i]=elem;
   }

まぁ、単なる複製なら、=演算子を使えばいいだけなのですが、
配列に格納したクラスのメッソドの返り値を別の配列に格納するとかの場合、使い勝手が悪い。
末尾追加はreserveで予め領域を確保した場合でも、判定が入る分微妙に遅くなります。

普通のforで書くしか無い。
   QVector<int> array{0,1,2,3,4,5,6,7,8};
   QVector<int> array2(array.count());
   //普通のforで書く
   for(int i=0;i<array.count();i++){
       //何回目か分かる でも 面倒くさい
       array2[i]=array[i];
   }
ループの度に、一々判定式と再初期化式を書くのは、うっかりミスの温床なので、できれば避けたい。

添字を返すイテレータ 


   QVector<int> array{0,1,2,3,4,5,6,7,8};
   QVector<int> array2(array.count());
   for(const int i:SmartCountor(array.count())){
       //何回目か分かる
       array2[i]=array[i];
   }
SmartCounterはイテレータを返すコンテナ、コンストラクタで指定された回数ループします、
副産物として、普通のforと違い、添字に使う整数にconst修飾ができます。

第一引数に始点、第二引数に、ループ回数を設定できます。
   QVector<int> array{0,1,2,3,4,5,6,7,8};
   QVector<int> array2(array.count());
   //第一引数に 開始位置 第二引数に ループ回数 *終了位置ではないので注意 
   for(const int i:SmartCountor(4,array.count()-4)){
       //何回目か分かる
       array2[i]=array[i];
   }
この場合第一引数には負数も設定可能です。
そのまま配列へのアクセスとして使うと、領域違反が起きるので注意、
それ以外の引数に負数が与えられた場合、自動的に0になります。

以下ソースコード

#ifndef SMARTCOUNTER_H
#define SMARTCOUNTER_H


//範囲for用の  添字イテレータ intを返す
class CountIterator
{
public:
    constexpr CountIterator(int set):index(set){;}
   //デリファレンス
    constexpr int operator *() const
    {
        return index;
    }
    //非一致判定
    constexpr bool operator !=(CountIterator set) const{
        return (index!=set.Index())?(true):(false);
    }
    //インクリメント
    void operator ++()
    {
        index++;
    }
    //比較用関数
    constexpr int Index() const {
        return index;
    }
private:
    int index;
};

//範囲for用のカウンタ
class SmartCountor
{
public:
    constexpr SmartCountor(int size):
        index(0),countSize(((size<0)?(0):(size))){;}
    constexpr SmartCountor(const int pos,const int length):
        index(pos),countSize(((length)<0)?(pos):(pos+length)){}
    constexpr CountIterator begin() const {return CountIterator(index);}
    constexpr CountIterator end() const {return CountIterator(countSize);}
private:
    int index;
    int countSize;
};


#endif // SMARTCOUNTER_H