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


2013年12月27日金曜日

QMutexについて

QMutexの使い方を調べたのでメモ

QMutex

QMutexはlockとunlockの間変数へのアクセスを禁止出来る。


        //変数への操作をロック
        mutex.lock();
        //読み出し
        QVector<int> out=data;
        //ロック解除
        mutex.unlock();

これだけだと、lock(),unlock()分処理が遅くなるだけで、何が面白いのか良く分からない、
        //変数への操作をロック
        mutex.lock();
        //読み出し
        data=set;
        //ロック解除
        mutex.unlock();
書き込み時にも使う事で、アトミックなデータアクセスが可能になる。
もしQVector<int> data;に変更があった時、別スレッドで読みだそうとすると。
mutex.unlock()が呼び出されるまで、読み出しが後回しにされる、
なので途中の中途半端な値にアクセスしてしまう事を回避できる。


サンプルコード
マルチスレッドで、同じクラスの配列に書き込みを行う、
各スレッドは、 isData()で配列を受け取り。
指定された値で全要素を埋めた後に、setData()で書き戻す。
0.1秒毎に、配列の要素が全て一致しているかチェックし。表示、全要素一致ならtrue、一致しないならfalse

MutexTest.pro

#-------------------------------------------------
#
# Project created by QtCreator 2013-12-27T13:41:25
#
#-------------------------------------------------

QT       += core
QMAKE_CXXFLAGS += -std=c++11

QT       -= gui

TARGET = MutexTest
CONFIG   += console
CONFIG   -= app_bundle

TEMPLATE = app


SOURCES += main.cpp \
    manageobject.cpp

HEADERS += \
    arraycalc.h \
    manageobject.h


arraycalc.h

#ifndef ARRAYCALC_H
#define ARRAYCALC_H
#include <QVector>
#include <QObject>
#include <memory>
#include <QMutex>

//データ保持
class Data
{
public:
    Data() {}
    QVector<int> isData(){
        //変数への操作をロック
        mutex.lock();
        //読み出し
        QVector<int> out=data;
        //ロック解除
        mutex.unlock();
        return out;
    }

    void setData(QVector<int> set){
        //変数への操作をロック
        mutex.lock();
        //書き込み
        data=set;
        //ロック解除
        mutex.unlock();
    }

private:
    QVector<int> data=QVector<int>(10000);
    QMutex mutex;
};



//配列操作クラス
class ArrayCalc :public QObject
{
    Q_OBJECT
public:
    //コンストラクタ
    explicit ArrayCalc(QObject *parent = 0):QObject(parent){}
    //データのセット
    void setData(std::shared_ptr<Data> setdata,const int setnum){
        data=setdata;
        num=setnum;
    }

signals:
    //処理終了のシグナル
    void WorkEnd();
public slots:
   //処理
    void doWork(){
        if(data){
            //配列の取得
            auto array=data->isData();
            if(array.count()>0){
                //設定値で配列を埋める
                array.fill(num);
            }
            //変更した配列の適用
            data->setData(array);
        }
        WorkEnd();
    }
private:
    std::shared_ptr<Data> data;
    int num;
};



#endif // ARRAYCALC_H


manageobject.h

#ifndef MANAGEOBJECT_H
#define MANAGEOBJECT_H

#include <QObject>
#include <QThread>
#include "arraycalc.h"
#include <memory>
#include <QDebug>
#include <QTimer>

//実行スレッドの管理クラス
class ManageObject : public QObject
{
    Q_OBJECT
public:
    explicit ManageObject(QObject *parent = 0);
private:
    //実行スレッド
    QThread *thread1;
    QThread *thread2;
    //処理スレッド
    ArrayCalc calc1;
    ArrayCalc calc2;
    //処理データ
    std::shared_ptr<Data> data;
    //タイマー
    QTimer *timer;
signals:
    void start1();
    void start2();
public slots:
    void finish1();
    void finish2();
    void on_timer();
private:
    void print();
   // void on_timer();
};

#endif // MANAGEOBJECT_H


manageobject.cpp

#include "manageobject.h"

ManageObject::ManageObject(QObject *parent) :
    QObject(parent)
{
    data=std::make_shared<Data>();
    //スレッド生成
    thread1=new QThread(this);
    thread2=new QThread(this);
    //処理データ設定
    calc1.setData(data,10);
    calc2.setData(data,-9);
    //親オブジェクトのリセット
    calc1.setParent(nullptr);
    calc2.setParent(nullptr);
    //スレッド移動
    calc1.moveToThread(thread1);
    calc2.moveToThread(thread2);
    //接続
    //処理の開始
    connect(this,SIGNAL(start1()),&calc1,SLOT(doWork()));
    connect(this,SIGNAL(start1()),&calc2,SLOT(doWork()));
    //処理終了
    connect(&calc1,SIGNAL(WorkEnd()),this,SLOT(finish1()));
    connect(&calc2,SIGNAL(WorkEnd()),this,SLOT(finish2()));
    //スレッド実行
    thread1->start();
    thread2->start();
    //シグナル呼び出し
    start2();
    start1();
    //表示タイマー設定
    timer=new QTimer(this);
    connect(timer,SIGNAL(timeout()),this,SLOT(on_timer()));
    timer->start(100);
}


//処理終了の通知
void ManageObject::finish1()
{
    start1();
}

void ManageObject::finish2()
{
    start2();
}

//タイマー起動
void ManageObject::on_timer()
{
    print();
}

//表示
void ManageObject::print()
{
    auto array=data->isData();
    if(array.count()>0){
        //配列の数字が全て一致しているか調べる falseなら競合発生
        auto chack=[](QVector<int> array){
            const int setnum=array[0];
            for(auto elem:array){
                if(setnum!=elem){
                    return false;
                }
            }
            return true;
        };
        qDebug() << chack(array) << array[0];
    }
}


main.cpp

#include <QCoreApplication>
#include <arraycalc.h>
#include <manageobject.h>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    ManageObject manage;
    return a.exec();
}



このサンプルコードは、スレッドから終了シグナルを受け取とるとすぐ再度スレッドを実行する。
そのためCPU負荷が高い、