ある三角形の頂点のうち、最後の頂点を求める方法
今日は小ネタです。
三角形ABCの各頂点の座標が、a, b, c であるものとします。
この三角形ABCのうち、外から渡した座標 d, e のいずれでもない、最後の頂点を求めて下さい。ただし d, e は、必ず a, b, c のうちのいずれかと一致し、また d≠e であるものとします。
答え:a + b + c - d - e
案外、思いつかないものです。
ちなみにこの方法は、a〜e が頂点座標ではなく、頂点配列のインデックスの場合にも利用できます。
似たようなテクニックに、「3つの数字のうち、真ん中の値を求める」のがあります。
template <class T> T middle(const T& a, const T& b, const T& c) { return a + b + c - std::min(a, std::min(b, c)) - std::max(a, std::max(b, c)); }
☆CSSテクニック☆Borderの継ぎ目
まず最初に、ありがちな次のようなものを作ってみました。
HTMLはだいたいこんな感じ。
<!doctype html> <html> <head> <meta charset="UTF-8" /> <title>HTML+CSS3で作る×ボタン</title> <style> .popup { background-color: white; color: #888; border: solid 1px; border-radius: 10px; position: absolute; top: 5em; left: 5em; width: 8em; height: 10em; } .popup > .content { position: absolute; top: 0.5em ; bottom: 0.5em ; left: 0.5em ; right: 0.5em ; color: black; border: solid 1px; } .popup > .close { border: inherit; border-radius: inherit; width: 14px; height: 14px; position: absolute; top: -7px; right: -7px; overflow: hidden; } .popup > .close > .base { border: solid 10px ; width: 0px; height: 0px; position: absolute; } .popup > .close > .border { border: solid 14px transparent; width: 0px; height: 0px; position: absolute; } .popup > .close:hover { color: red; border-color: red; } .popup > .close > .l { border-left: solid white 14px; top: -7px; left: -8px; } .popup > .close > .r { border-right: solid white 14px; top: -7px; left: -6px; } .popup > .close > .t { border-top: solid white 14px; top: -8px; left: -7px; } .popup > .close > .b { border-bottom: solid white 14px; top: -6px; right: -7px; } </style> </head> <body style="background-color:#FFC0FF"> <div class="popup"> <div class="close"> <div class="base"></div> <div class="border l"></div> <div class="border r"></div> <div class="border t"></div> <div class="border b"></div> </div> <div class="content"> <p>Contents</p> </div> </div> </body> </html>
右上の×印を、borderを用いて作った4つの三角で切り取って作っています。それぞれ、中心から微妙に上下左右に1pxずつずらすことにより、×型の隙間を作っている……という形ですね。
.base なる要素があるのは、わざわざborder-colorとbackground-colorを個別に設定しなくても、colorを設定するだけで一気に色が変わるようにするための工夫です。
これを実際に適用すると、次のようになりました:
※Windows版IOpera15.0
※Windows版IFirefox 22.0
※Mac OS X版Google Chrome 28.0
……なんかFirefoxだけ怪しい。
原因は
1ピクセルずらしをせずに、全く同じ場所に重ねてみると一目瞭然かと思います。
・Internet Explorer、Google Chrome
Borderの継ぎ目にアンチエイリアスがかかっていることがわかります。
・Windows版Firefox
よく見ると、微妙にアンチエイリアスがかかってるような気がしないでもありません。
が……背景色が#7d7d7dだというのに、そのアンチエイリアスっぽいところは#787878だとか#797979だとか。より暗くなっている……何故??
試しに、他の辺のborder-colorをtransparentではなく#888(背景色。実際には#7d7d7dになるのは恐らくOS側が原因)にしてみます。結果は……。
どうやらWindows版Firefox 22.0では、透明borderと不透明borderをブレンドする辺りが上手く行っていないっぽいような予感がしてきます。
・Mac OS X版Firefox
こちらはそもそも、アンチエイリアスが表示されません。境界線上にあるピクセルは、反時計回りの側のborderの情報をそのまま使っています。
Windows版と同様に、他のborderを#888にしてみましたが、結果は同じ。最初からそういう機能自体がないようです(border-radius等のアンチエイリアスはできるのですが……)。
これらを踏まえて
Windows版Firefoxは少々太い×になってしまうけれども問題はないものとして、Mac版Firefoxだけ微妙に1ピクセルずつずらした×を作る、ということにすれば、軒並み綺麗な×が作れるような気がします。
JavaScriptとFirefox専用のCSSハックを組み合わせて、こんな感じ。
<style> /* (省略) */ .popup > .close > .l { border-left: solid white 14px; bottom: -7px; left: -8px; } .popup > .close > .r { border-right: solid white 14px; top: -7px; right: -8px; } .popup > .close > .t { border-top: solid white 14px; top: -8px; left: -7px; } .popup > .close > .b { border-bottom: solid white 14px; bottom: -8px; right: -7px; } </style>
<script> jQuery.ready(function() { if (navigator.platform.match(/^Mac/i)) { jQuery('head').append('<style> @-moz-document url-prefix() { .popup > .close { width: 15px; height: 15px; } } </style>'); } }) ; </script>
上下左右の三角の基準点を反時計回り側の頂点にしておくことで、元は14x14ピクセルの大きさで作っていた×ボタンを15x15にするだけで上手くゆくようになります。
余談
どうやら完璧に見えたGoogle Chrome 28.0でしたが、非transparentなborderが複数になった途端、アンチエイリアスがかからなくなる模様。
もっとも、×印を作る時には各辺バラバラに作るので、関係のないことですが。
今度はiOSで散布図アニメーションを試してみた
セルフインスパイアな記事。先日Pythonによる散布図アニメーションを取り上げました。
pythonで散布図アニメーションを試してみた - 株式会社CFlatの明後日スタイルのブログ
これと同じようなことをiPhoneでやってみました。
事前準備
アニメーションに使う動画を適当に入手し、フレームを画像にして出力しておきます。
$ ffmpeg -i input.mp4 -f image2 frame%03d.jpg
開発環境
iOS開発なのでXcodeを使います。XcodeでSingle View Applicationあたりで新規プロジェクトを作ります。
また、オープンソースのライブラリも必要となります。
CocoaPodsを導入し(CocoaPods.org - The Dependency Manager for Objective C.)、
依存するライブラリを$ pod install
します。
プロジェクトのルートディレクトリにPodfile
を作り次の内容を記載、からの$ pod install
でインストールが完了するのでxcworkspace
のほうを開きます。
# Podfile pod 'CorePlot' # グラフ描画 pod 'OpenCV' # 画像処理
また、事前準備で作った画像ファイルをプログラムから使うためにプロジェクトに追加しておきます。
まずは結果から
コード紹介
全体の流れはこんな感じ
- 起動時に各画像を読み込んで散布図用の点を計算しておく
- スタートボタンでタイマーをスタート
- 時間が進むごとにグラフを書き換える
画像の読み込み
Single View Applicationのサンプルアプリケーションなので細かいことは気にせず、viewDidLoad
で画像の読み込みを行います。
ここでは100個分を読み込み対象とし、OpenCVのFastFeatureDetectorで抽出した特徴点(X,Y)のvectorを得て、それを1コマ分として_plots
というvectorに保持しておきました。
// ViewController.mm <- C++を使うために.mではなく.mmに変更 #import "ViewController.h" #import <opencv2/opencv.hpp> #import <opencv2/highgui/ios.h> #import <vector> std::vector< std::vector<cv::KeyPoint> > _plots; - (void)viewDidLoad { // ... // あらかじめ画像を読み込んで特徴量の検出を済ませておく [self loadFrames]; } - (void)loadFrames { for(NSUInteger i=1; i<=100; ++i){ UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"frame%03d.jpg", i]]; cv::Mat mat; UIImageToMat(image, mat); cv::FastFeatureDetector det = cv::FastFeatureDetector(); std::vector<cv::KeyPoint> points; det.detect(mat, points); // グラフのY軸と画像のY軸が反対なので値を反転する for(size_t j=0; j<points.size(); ++j){ points[j].pt.y = 360 - points[j].pt.y; } _plots.push_back(points); } }
タイマー処理
続いてアニメーションに必要なタイマー処理の部分です。最初の処理で100枚分の画像データを読み込みました。アニメーションをするには時間経過にしたがって表示するデータを少しずつ変えていけばいいのでタイマーを使います。
タイマーは標準のNSTimer
、0.1秒ごとにstepAnimation
を呼び出し、インデックスをインクリメントしています。このインデックスは表示データの配列を指すインデックスで、タイマーによってこのインデックスを変えていくことで描画するデータを切り替えていくということをやっています。
インデックスを増やしたら(または最初に戻したら)、グラフの再描画を明示的に呼び出します。[self.graph reloadData]
というのがそれに該当します。グラフについては次の節で。
NSUInteger _currentIndex; // 0に初期化しておく - (IBAction)start:(UIButton*)sender { if ([self.timer isValid] ){ [self.timer invalidate]; [sender setTitle:@"Start" forState:UIControlStateNormal]; }else{ self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(stepAnimation) userInfo:nil repeats:YES]; [sender setTitle:@"Stop" forState:UIControlStateNormal]; } } - (void)stepAnimation { _currentIndex++; if( _currentIndex >= _plots.size() ){ _currentIndex = 0; } [self.graph reloadData]; }
グラフ描画
Graphの描画にはCorePlotというライブラリを使用しました。汎用性の高いOSSのライブラリです。
core-plot - Cocoa plotting framework for OS X and iOS - Google Project Hosting
ビューやグラフを作る
グラフ表示周りのコードを紹介するにあたり、見た目上の細かい設定がかなり多くて煩雑になるので、その部分を省略して掲載します(最後にコード全文のリンクを紹介します)。
- (UIView*)createGraphView { CPTGraphHostingView *graphView = [[CPTGraphHostingView alloc] initWithFrame:rect]; CPTGraph *graph = [[CPTXYGraph alloc] initWithFrame:rect]; graphView.hostedGraph = graph; CPTScatterPlot *scatterPlot = [CPTScatterPlot new]; scatterPlot.dataSource = self; [graph addPlot:scatterPlot]; self.graph = graph; return graphView; }
イメージ的にはこんな感じの階層で参照を持つようになっています。
- CPTGraphHostingView
- CPTGraph
- CPTScatterPlot
- CPTGraph
CPTGraphHostingView
には対応するCPTGraph
があって、そのCPTGraph
のなかにはCPTScatterPlot
やその他のプロット(棒グラフなど)を複数持てるようになっています。
生成したCPTGraphHostingView
はaddSubView:
しておきます。
- (void)viewDidLoad { // ... // グラフ描画の設定 UIView* graphView = [self createGraphView]; [self.view addSubview:graphView]; }
表示データの取り扱い
CorePlotで表示するデータはDataSource
プロトコルを実装することでCorePlot側に教えてあげる仕組みになっています。UITableView
とほぼ同じ仕組みなのでiOS開発者であれば問題ないはず。
今回はViewController
をそのままDataSourceにします。
// ViewController.h #import <UIKit/UIKit.h> #import <CorePlot/CorePlot-CocoaTouch.h> @interface ViewController : UIViewController<CPTPlotDataSource> @end
あとはDataSourceのインターフェースを実装してあげればいいのですが、そのときにタイマーによって変化するインデックスに応じたデータを返すことでアニメーションを実現しているわけです。
// プロットするデータの数を返す - (NSUInteger)numberOfRecordsForPlot:(CPTPlot *)plot { return _plots[_currentIndex].size(); } // idx番目に関する値を返す // XなのかYなのかはfieldNumに応じて変える - (NSNumber *)numberForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)idx { NSNumber *num; std::vector<cv::KeyPoint>& points = _plots[_currentIndex]; switch (fieldEnum) { case CPTScatterPlotFieldX: num = [NSNumber numberWithFloat:points[idx].pt.x]; break; case CPTScatterPlotFieldY: num = [NSNumber numberWithFloat:points[idx].pt.y]; default: break; } return num; }
これでグラフの描画もできました。起動してボタンを押せばアニメーションがスタートします!
ソースコード
boost::multi_arrayのランダムアクセスに注意
※下記の内容はVisual Studio2010でコンパイルしました。gcc 4.8.2でコンパイルすると結果が異なります。詳しくはコメント欄をご確認下さい。
便利なboost::multi_array
多次元配列を実装するときに便利なboost::multi_arrayですが、ランダムアクセスが遅いので注意が必要です。 この件について意外と情報が少なかったのでまとめてみました。
サンプルプログラム
#include "stdafx.h" #include <iostream> #include <boost/timer.hpp> #include <boost/multi_array.hpp> int _tmain(int argc, _TCHAR* argv[]) { boost::multi_array<int, 3> multiArray( boost::extents[500][500][500] ); boost::timer t; t.restart(); for ( int i = 0 ; i < multiArray.shape()[0] ; ++i ) { for ( int j = 0 ; j < multiArray.shape()[1] ; ++j ) { for ( int k = 0 ; k < multiArray.shape()[2] ; ++k ) { multiArray[i][j][k] = i + j + k; } } } std::cout << "ijk time = " << t.elapsed() << std::endl; t.restart(); for ( int k = 0 ; k < multiArray.shape()[2] ; ++k ) { for ( int j = 0 ; j < multiArray.shape()[1] ; ++j ) { for ( int i = 0 ; i < multiArray.shape()[0] ; ++i ) { multiArray[i][j][k] = i + j + k; } } } std::cout << "kji time = " << t.elapsed() << std::endl; t.restart(); int* it = multiArray.origin(); for ( int i = 0 ; i < multiArray.shape()[0] ; ++i ) { for ( int j = 0 ; j < multiArray.shape()[1] ; ++j ) { for ( int k = 0 ; k < multiArray.shape()[2] ; ++k, ++it ) { *it = i + j + k; } } } std::cout << "iterator time = " << t.elapsed() << std::endl; return 0; }
実行結果
ijk time = 2.293 kji time = 3.883 iterator time = 0.074
iteratorを使用することによって、ランダムアクセスより10倍以上速くなっています。
一挙紹介!動画アプリで使ったCocoaPods
もはやiOS開発に欠かせない存在になったCocoaPods。先日弊社からリリースしたアプリでも、複雑なUIやありがちな機能を簡単に実装するものから実装を少しラクにするようなものまで、いろいろなものを使っています。
iPhoneで気軽にスポーツ動画を楽しめるアプリケーションをリリースしました。AppStoreにて配信中です。このアプリではスポーツのハイライトや試合結果、最新ニュースなどの動画を検索することなく閲覧することができます。スポーツが好きな方、スキマ時間を有効に使いたい方に最適な動画アプリです。
UI/コントロール系
MNMBottomPullToRefresh
MNMBottomPullToRefresh for iOS - Cocoa Controls
「上に引っ張ったら更新する」機能を実現するものです。
iOS標準では下に引っ張って更新を実現する方法は用意されていないので自分でやるためには色々頑張る必要がありますが、MNMBottomPullToRefresh利用することで簡単に実現できました。
MMDrawerController
Search results for mmdrawer - Cocoa Controls
メインコンテンツの左右にサイドバーを配置するためのコントロールです。一昔前のFacebookのiPhone版がこんな感じのインターフェースでしたね。
安易にサイドバーを配置することの賛否はあるとは思いますが、サイドバーを置くとなったらとても使いやすいライブラリでした。
XCDYouTubeVideoPlayerViewController
XCDYouTubeVideoPlayerViewController for iOS - Cocoa Controls
標準のMPMediaPlayerController
では動画のリソースを特定する必要があるため、YouTubeの動画IDだけでは再生できません。
XCDYouTubeVideoPlayerViewControllerは、MPMediaPlayerController
を継承しているクラスです。YouTubeの動画IDから動画自体のURIを解析してくれる機能をもっているため、動画IDから動画を再生するまでがすごく楽でした。
FontAwesome-iOS
Webでよく使われているFontAwesomeをiOSでも使えるようにするライブラリです。ちょっとしたアイコンなら画像なしで表現できるようになるのでとても楽です。
iRate
iRate for iOS - Cocoa Controls
iRateはアプリ起動時に「アプリを評価してね〜」のダイアログを出すための拡張です。この機能はユーザーに負荷をかけることになるのでいい感じのタイミングで出さないと評価を下げることになってしまうのですが、iRateは起動回数や経過日数などから使ってくれているユーザーに対してのみダイアログを出してくれるという仕組みになっています。
ユーザービリティを多少損なうことにはなりますが、レビューへの導線も大切なので入れておきたいところです。
SVProgressHUD
Search results for svprogress - Cocoa Controls
SVProgressHUDを使うと標準のActivityIndicatorよりもちょっとかっこ良くて使いやすいインジケータが使えるようになります。
ライブラリ/拡張
MWFeedParser
MWFeedParser for iOS - Cocoa Controls
RSSフィードを解析するライブラリの中では一番使われている(気がする)ライブラリです。RSSのパースは自分では絶対に書きたくない処理(DRY!)なのでこれを使うのが今のところ一番いいのかなと思います。
NSDate+TimeAgo
Objective-CのNSDate
クラスのカテゴリで、相対日時の文字列を返してくれるインターフェースが提供されます。
// githubより転載 NSDate *date = [[NSDate alloc] initWithTimeIntervalSince1970:0]; NSString *ago = [date timeAgo]; NSLog(@"Output is: \"%@\"", ago); 2011-11-12 17:19:25.608 Proj[0:0] Output is: "41 years ago"
LinqToObjectiveC
ColinEberhardt/LinqToObjectiveC
Objective-CのNSArray
やNSDictionary
のカテゴリで、Linqのようなインターフェースが使えるようになるライブラリです。
内部ではforループを回しているだけなのですが、場合によってはより読みやすいコードにすることができるので使用しました。
// githubより転載 LINQSelector surnameSelector = ^id(id person){ return [person name]; }; LINQAccumulator csvAccumulator = ^id(id item, id aggregate) { return [NSString stringWithFormat:@"%@, %@", aggregate, item]; }; NSArray* surnamesList = [[[[people linq_select:surnameSelector] linq_sort] linq_distinct] linq_aggregate:csvAccumulator];
上の例のNSArray* surnamesList = ...
のところがすごく簡潔に書けていると思います。
- peopleが入っているArrayを
- nameプロパティのArrayにして(
linq_select
) - ソートして(
linq_sort
) - 重複を取り除いて(
linq_distinct
) - カンマ区切りの文字列にまとめる(
linq_aggregate
)
複数種類のiPhoneアプリを同時開発するときのTips
先日弊社からiPhoneアプリをリリースしました。
iPhoneで気軽にスポーツ動画を楽しめるアプリケーションをリリースしました。AppStoreにて配信中です。このアプリではスポーツのハイライトや試合結果、最新ニュースなどの動画を検索することなく閲覧することができます。スポーツが好きな方、スキマ時間を有効に使いたい方に最適な動画アプリです。
このアプリはあるスポーツについての最新の試合結果などを検索せずに見ることができるというものです。アプリはスポーツ毎に分かれているので、サッカーを見たい人はサッカー動画用のアプリ、野球を見たい人は野球用のアプリを使うことになります。
ここまで言うと大体お分かりいただけると思うのですが、アプリの基本部分の作りはどのアプリにも共通したものになっています。つまりソースコードが共有されているわけです。アプリ間で異なるのはアイコン画像やアプリ名、まとめた動画情報など。また内部ではParse.comのIDや広告のIDなどもそうですね。逆に言うとそれ以外の部分が共通しているということです。
このようなケース、ほとんどのソースコードは一緒だけど一部だけ違うアプリを複数同時にリリースするというケースではいろいろなことが面倒になります。
もちろん10種類分のアプリがあるので手動ではやってられないため、できるところはほとんどを自動化することにしました。
このようなケースはあまりないのかもしれませんが、今日の記事ではどのようなやり方をしたかを紹介してみたいと思います。
ソース管理
ほとんどのソースコードが共通なので同じGitリポジトリで管理することにしています。そして各スポーツに対して一つずつブランチを用意します。
$ git br -a 01.soccer 02.cricket # ... 10.icehockey master * work-branch01
こんな風に10個分のブランチを用意しました。
全体に関わる変更
全体に関わるコミットはトピックブランチを経てmasterブランチに適用されます。そしてそれらの変更は$ git rebase master
によって各ブランチに取り込まれることになります。
10個のブランチに対してrebase
コマンドを叩いていくのは非常に面倒です。そこで私はRubyのrake
を使うことにしました。
$ rake rebase
とすると、ブランチを切り替えながら$ git rebase master
してくれるというスクリプトです。
(ブランチ名の一覧はyaml形式のデータファイルの書いておいてそこから取っています。$ git branch -a
の結果を使って頑張ることもできますが、別の用途で作ったyamlがあったのでそちらを利用。)
desc "Run 'git rebase master' on all sports branches" task :rebase do config = YAML.load_file( File.expand_path("../../data.yml", __FILE__) ) config.keys.each_with_index do |key, i| branch_name = "#{sprintf('%02d', i+1)}.#{key}" system "git checkout #{branch_name}" system "git rebase master" system "git checkout ." end system "git checkout master" end
各ブランチに対する変更
スポーツ毎に異なるものとして次のようなものがあります。
- 各種外部サービスのキーやトークン
- ホーム画面での表示名
- 動画用データの参照先
- アイコン
- ProvisioningProfile(*)
- bundle id(*)
大体こんなところでした。このうち(*)がついていないものは、各スポーツごとのブランチに対して手動でコミットしておきました。残りの(*)で印をした項目はプログラムを書いて動的に変更するようにしました。
というのは、これらのファイルを変更してコミットしてしまうとmasterに対してrebaseを行うときに100%コンフリクトしてしまうために自動処理ができなくなってしまうわけです。具体的にはpbxproj
ファイルとplist
ファイルがそれにあたります。
ProvisioningProfileやbundle idがを変更する必要があるのは各バージョンをビルドするときです。基本的には放っておき、ビルドする際にpbxproj
ファイルとplist
ファイル内の文字列の置換により値を置き換えます。値の対応付けは外部のyamlファイルに書いておきました。
ビルドの自動化
ブランチを切り替えながら10個分のビルドを済ませるのも一苦労です。ここでもrakeを用いた自動化を行っています。
require "yaml" desc "create xcarchive and ipa" task :archive do time = Time.now archive_dir = time.strftime("archive_%Y%m%d%H%M%S") ipa_dir = time.strftime("ipa_%Y%m%d%H%M%S") Dir.mkdir(archive_dir) Dir.mkdir(ipa_dir) profiles = YAML.load_file( File.expand_path("../profiles.yml", __FILE__) ) profiles.each do |k,v| next if k == "master" # change branch system "git co . && git co #{k}" # アイコン、表示名、プロビジョニングを変更するrakeタスク system "rake provision" # create archive system %Q{ xcodebuild \ -workspace MyApp.xcworkspace \ -scheme MyApp \ -sdk iphoneos \ -configuration Release \ archive -archivePath #{archive_dir}/#{k} } ipa_path = File.expand_path("../../#{ipa_dir}", __FILE__) # create ipa system %Q{ xcrun \ -sdk iphoneos \ PackageApplication #{archive_dir}/#{k}.xcarchive/Products/Applications/MyApp.app \ -o #{ipa_path}/#{k}.ipa \ -embed ~/Library/MobileDevice/Provisioning Profiles/#{v['release']}.mobileprovision } end system "git co master" end
- 10個分のブランチに対してループを回す
- 対象ブランチをチェックアウト
- アイコン、表示名、プロビジョニングを変更するタスクを実行
xcodebuild
コマンドでxcarchiveを作るxcrun
コマンドでxcarchiveからipaファイルを作る
xcodebuild
やxcrun
がここでの肝です。使い方は上のコードを見てもらえば大体わかってもらえると思います。
リリース(アップロード)の自動化
iTunesConnectへのアップロードもなかなか骨が折れます。アプリを作って紹介文を書いてスクリーンショットを登録してアップロード待ち状態にして、、、という作業を10個分やる必要があるので。
幸い、そこまでを手動でやっておけばあとは自動でアップロードを行うことができます。
さっきコマンドで作ったipaファイルとxcrun
コマンドを使います。
$ security add-generic-password -s Xcode:itunesconnect.apple.com -a YOUR_ID -w YOUR_PASSWORD -U $ xcrun -sdk iphoneos Validation -online -upload -verbose "path to ipa" # 10種類分.... $ security delete-generic-password -s Xcode:itunesconnect.apple.com -a YOUR_ID
iTuneConnectへのログインが必要になるので、xcrun
コマンドの前後にsecurity
コマンドによる認証情報の追加と削除が必要なことに注意。
スクリーンショット撮影の自動化
10種類分のスクリーンショットを撮るのも一苦労なので、そこも自動化しました。本来はUIのテストに用いるKIFというライブラリを使いました。
60分で始めるiOSアプリのUI自動テスト - 株式会社CFlatの明後日スタイルのブログ
KIFを使うことで自動で画面遷移をさせることができるので、画面遷移をしつつスクリーンショットが欲しいところで画面を画像ファイルに書き出せばいい感じで自動化できます。
表示中の画面を画像に書き出す方法は次の通りです。
- (void)captureScreen:(NSString*)name { NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString* dir = [paths objectAtIndex:0]; UIWindow* window = [[UIApplication sharedApplication] keyWindow]; UIGraphicsBeginImageContextWithOptions(window.bounds.size, NO, 0); CGContextRef context = UIGraphicsGetCurrentContext(); for (UIWindow* win in [[UIApplication sharedApplication] windows]) { [win.layer renderInContext:context]; } UIImage* image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); [UIImagePNGRepresentation(image) writeToFile:[dir stringByAppendingPathComponent:name] atomically:YES]; }
なおこの方法ではステータスバーの部分は描画されませんが、今回はそれで良しとしました。回避策としては、ダミーのステータスバー画像を用意しておいて出力画像に合成させるというものがあります。
KIFを使う=テストの実行ということになります。テストもxcodebuild
で実行できるはずなので、本来はブランチ切替→テストも完全自動化できると思いますが、このときは手動でテストを実行していたので検証はしていません。
最後に
今回のアプリのように複数種類を同時に扱うには自動化が不可欠でした。コマンドラインでいろいろサポートされていて助かりました。
iTunesConnectでの操作(アプリ追加、バージョン追加、スクリーンショット追加など)もコマンドラインでできるととても助かるのですが。。。
CUDAでプログラミングする前にやっておきたいこと
こちらの記事もご確認下さい。
CUDA6.0用にCPUとGPUの速度比較コードを修正 - 株式会社CFlatの明後日スタイルのブログ
GPUとCPUの速度比較をしたい
以前CUDAでのプログラミングが完成した際に、CPUとの速度比較を行いたいという当たり前の要望が上がりました。 そこでGPUとCPUを切り替えるコードを作成したのですが、どうせなら最初から作っておくべきだったと反省しました。 今回はその際に作成したGPUとCPUの速度比較コードを公開します。
OpenMPの設定
GPUとCPUの速度比較をする際にCPUの方も並列化しないと不公平です。CPUの並列化には設定が簡単なOpenMPを使います。 CUDAプロジェクトでOpenMPを使用できるようにするには、プロジェクトのプロパティを開き、構成プロパティ/CUDA C/C++/Command Lineの追加オプションに-Xcompiler "/openmp"と記載します。 あとは並列化したいfor文の前に#pragma omp parallel forを付け加えるだけです。 CUDAでのプログラミングが初めての方はこの記事も参考にして下さい。
GPUとCPUの切り替えを行うラッピング
下記にあるSwitchableCPUGPU.cuhとTimer.cuh内で宣言されたマクロを呼ぶと、USE_GPUの切り替えでGPUとCPUを切り替えることができます。
SwitchableCPUGPU.cuh
#pragma once #include "Timer.cuh" #define USE_GPU #ifdef USE_GPU #define SWITCHABLE_DEVICE __device__ #define SWITCHABLE_GLOBAL __global__ #define SWITCHABLE_TIMER CudaEventTimer #else #define SWITCHABLE_DEVICE #define SWITCHABLE_GLOBAL #define SWITCHABLE_TIMER Timer #endif template <typename T> void SwitchableCudaMalloc( T& val, int size ) { #ifdef USE_GPU cudaMalloc( (void**)&val, sizeof(T)*size ); #endif } template <typename T> void SwitchableCudaFree( T* val ) { #ifdef USE_GPU cudaFree( val ); #endif } template <typename T> void SwitchableCudaMemcpyHostToDevice( const T* const host, T* const device, int size ) { #ifdef USE_GPU cudaMemcpy( device, host, sizeof(T)*size, cudaMemcpyHostToDevice ); #endif } template <typename T> void SwitchableCudaMemcpyDeviceToHost( const T* const device, T* const host, int size ) { #ifdef USE_GPU cudaMemcpy( host, device, sizeof(T)*size, cudaMemcpyDeviceToHost); #endif }
Timer.cuh
#pragma once #include <string> #include <time.h> class CudaEventTimer { public : CudaEventTimer( const std::string& message ) : m_message( message ) { cudaEventCreate(&m_start); cudaEventCreate(&m_end); cudaEventRecord( m_start, 0 ); } ~CudaEventTimer() { cudaEventRecord( m_end, 0 ); cudaEventSynchronize( m_end ); float time; cudaEventElapsedTime( &time, m_start, m_end ); printf("%s = %f sec.\n",m_message.c_str(), time*0.001); cudaEventDestroy( m_start ); cudaEventDestroy( m_end ); } private: cudaEvent_t m_start; cudaEvent_t m_end; std::string m_message; }; class Timer { public : Timer( const std::string& message ) : m_message( message ) { m_start = clock(); } ~Timer() { m_end = clock(); printf("%s = %f sec.\n",m_message.c_str(), (double)(m_end - m_start)/CLOCKS_PER_SEC); } private: clock_t m_start; clock_t m_end; std::string m_message; };
行列計算でGPUとCPUを比較
下記が行列の掛け算を行うテストコードです。CUDA関連の処理をラッピングされた関数から呼ぶことによって、GPUとCPUの切り替えを容易にしています。
#include <stdio.h> #include "SwitchableCPUGPU.cuh" SWITCHABLE_GLOBAL void Calculate( float* matrixA, float* matrixB, float* matrixC, int iLength, int col = 0, int row = 0 ) { #ifdef USE_GPU row = blockIdx.x * blockDim.x + threadIdx.x; col = blockIdx.y * blockDim.y + threadIdx.y; if ( row > iLength || col > iLength ) return; #endif float target = 0.0f; for ( int i = 0 ; i < iLength ; ++i ) { target += matrixA[row*iLength + i] * matrixB[i*iLength + col]; } matrixC[row*iLength + col] = target; } int main() { // 行列のサイズ決定 const int iLength = 1024; const int iSize = iLength * iLength; // CPU側の変数初期化 float* matrixA = (float*)malloc(sizeof(float)*iSize); float* matrixB = (float*)malloc(sizeof(float)*iSize); float* matrixC = (float*)malloc(sizeof(float)*iSize); for ( int col = 0; col < iLength ; ++col ){ for ( int row = 0; row < iLength ; ++row ){ matrixA[col*iLength + row] = rand() % (1000); matrixB[col*iLength + row] = rand() % (1000); matrixC[col*iLength + row] = 0.0f; } } // ここから時間計測 SWITCHABLE_TIMER t("time"); // GPU側の変数初期化 float* d_matrixA; float* d_matrixB; float* d_matrixC; SwitchableCudaMalloc( d_matrixA, iSize ); SwitchableCudaMalloc( d_matrixB, iSize ); SwitchableCudaMalloc( d_matrixC, iSize ); SwitchableCudaMemcpyHostToDevice( matrixA, d_matrixA, iSize ); SwitchableCudaMemcpyHostToDevice( matrixB, d_matrixB, iSize ); // 行列計算 #ifdef USE_GPU const int iThread = 16; dim3 thread( iThread, iThread ); const int iBlock = ( iLength + iThread - 1 )/iThread; dim3 block( iBlock, iBlock ); Calculate<<<block, thread>>>( d_matrixA, d_matrixB, d_matrixC, iLength ); cudaThreadSynchronize(); #else #pragma omp parallel for for ( int i = 0 ; i < iLength ; ++i ) { for ( int j = 0 ; j < iLength ; ++j ) { Calculate( matrixA, matrixB, matrixC, iLength, i, j ); } } #endif // 後処理 SwitchableCudaMemcpyDeviceToHost( d_matrixC, matrixC, iSize ); free( matrixA ); free( matrixB ); free( matrixC ); SwitchableCudaFree( d_matrixA ); SwitchableCudaFree( d_matrixB ); SwitchableCudaFree( d_matrixC ); return 0; }
実行結果
実行結果は次のようになりました。参考までに並列化しなかったCPUの結果も載せておきます。 GPUには苦手な計算もあるので、OpenMPと比べて明らかに遅い場合はGPUの使用を検討した方が良いかもしれません。
GPU:0.152510s CPU OpenMP:1.220000s CPU 並列化無し:7.254000s
GPU計算の高速化
今回のプログラムでは全く行っていませんが、GPU計算には色々な高速化手法があります。 興味のある方は下記の書籍等を参考にして下さい。