.NET用のPDF操作ライブラリPdfSharpを使って、PDFファイルに含まれるJPEG画像のバイト列を抽出する。iTextではない。
「PdfSharp」NuGetパッケージをインストールして、以下の感じで一応OK。
StreamはStreamって言ってるけどSystem.IO.Streamではないのでバイト列をファイルごとに返すことにした。
参考:PDFsharp Sample: Export Images
ページ
▼
2015-08-23
2015-08-10
PowerShell と ImageMagick (Magick.NET)で画像を加工する
WindowsでImageMagickを使おうとすると、cmdの引数の扱いがだるいと思っていたのだけど、ImageMagickライブラリの.NETラッパーであるところのMagick.NETをPowerShellから呼んでしまえば、便利に呼べることに気づいた。convertコマンドのパラメータの順序とか考えなくていいので、正直convert使うより楽かもしれない。
基本的にMagick.NETを呼んでいけばOK(気づいたPowerShell固有の罠は以下に書いておく)。本格的に処理するのであればC#でコード書いたほうがいいと思うけど、そこまででもないワンタイムバッチ処理にどうぞ。ただし「Verb-Noun -Arg abc」styleでない、.NETクラス直呼び出しの「なんちゃってPowerShell」なので、がっかりされる前に宣言しておく。コンソールで打って問題ない量だと思うけど。
なお動かしてる環境は、PowerShell v4 / x64。
基本的に「New-Object」「何かして」「Write」の組み合わせ。
フォーマット指示が必要な場合、Writeの前に入れる。
あとは
(多分)EXIFにしたがって回転してくれるAutoOrient()ってのもある。便利ね。
そのResize()もパラメータをいくつか選べる。
一番簡単なやつがwidth, heightを指定するやつ(New-ObjectとWriteはここの説明では省略)
パーセンテージ指定が次に簡単(書き方がイマイチ...)
で、もっともいろいろできるのが、MagickGeometry指定パターン。ImageMagickをコマンドで使っている人ならおなじみの、例の記法が使える。例えば、アスペクト比を無視する!を付与したスタイル。
このあたりの挙動はImageMagickそのものの話なので、詳しくは公式ドキュメント(ImageMagick v6 Examples --
Resize or Scaling (General Techniques))を参照。なお、日本語でまとめてくれてる人もいる(ImageMagickでリサイズする方法)
ここまで書いておいて例が悪いな。
他のものを知りたかったら、$image.で[TAB]してみたり、MagickNetのドキュメントとか、ImageMagickのコマンドラインオプション(ImageMagick: Command-line Options)を参照。
Statistics()を呼んで、Composite()あるいはGetChannel(チャンネル)を呼ぶ。
先に
基本的にMagick.NETを呼んでいけばOK(気づいたPowerShell固有の罠は以下に書いておく)。本格的に処理するのであればC#でコード書いたほうがいいと思うけど、そこまででもないワンタイムバッチ処理にどうぞ。ただし「Verb-Noun -Arg abc」styleでない、.NETクラス直呼び出しの「なんちゃってPowerShell」なので、がっかりされる前に宣言しておく。コンソールで打って問題ない量だと思うけど。
なお動かしてる環境は、PowerShell v4 / x64。
準備
インストール
適当なフォルダを作り、NuGetでMagick.NETのx64 or x86を環境にあわせてもってくる。PS C:\> mkdir c:\opt\magick PS C:\> cd c:\opt\magick PS C:\opt\magick> Invoke-WebRequest https://nuget.org/nuget.exe -OutFile nuget.exe PS C:\opt\magick> .\nuget install Magick.NET-Q16-x64NuGet的にはAnyCPUがあるので、何も考えずにAnyCPUを選びたくなるところではあるが、それはNG。というのも、AnyCPU版はx86とx64のDLLをEmbedded Resourceでかかえる形になっていて、必要に応じてロードしているっぽいのだが、これがAdd-Typeでうまくロードできない。x64やx86はプラットフォーム依存DLLがそのまま置いてあるのでOK。
アセンブリのロード
Add-Typeするだけ。これはさすがにスクリプト化しておいたほうが便利かもね。PS C:\opt\magick> Add-Type -Path .\Magick.NET-*\lib\net40-client\Magick.NET-x64.dll
イメージの読み書き
イメージの読み込み
とにかくここから始まる。これはMagickImageインスタンスを作ればOK。PS c:\temp> $image = New-Object ImageMagick.MagickImage c:\temp\sample.jpgただし、ワーキングディレクトリはPowerShellのものではないので、フルパスで指定するのが無難。クラス名はNew-O[TAB] MagickI[TAB]で補完できるのでおすすめ。
イメージの書き込み
後述の加工作業を行ってから、.Write(path)で書く。PS C:\temp> $image.Write('c:\temp\sample-out.jpg')
基本的に「New-Object」「何かして」「Write」の組み合わせ。
フォーマット指示が必要な場合、Writeの前に入れる。
PS C:\temp> $image.Format = 'png' PS C:\temp> $image.Write('c:\temp\sample-out.png')とか、
PS C:\temp> $image.Format = 'jpeg' PS C:\temp> $image.Quality = 99 PS C:\temp> $image.Write('c:\temp\sample-out-q99.jpg')とか。
あとは
PS c:\temp> $image.Dispose()で後片付け。インタラクティブ操作時はpowershellごと消してもいいと思うけど、ループ時とかは注意。
情報の表示
属性の表示
$imageと打てば、それだけで情報がでてくる。Width, Heightなど。これだけでもわりと便利。ただ、計算するプロパティが含まれているせいか、全部出すと重いのでループするのはおすすめしない。表示はこんな感じ(全部ではない):EXIF情報の表示
$image.GetExifProfile().Values でOK。PS C:\temp> $image.GetExifProfile().Values DataType IsArray Tag Value -------- ------- --- ----- Ascii False ImageDescription Ascii False Make SONY Ascii False Model NEX-6 Short False Orientation 1 Rational False XResolution 350 Rational False YResolution 350 Short False ResolutionUnit 2 Ascii False Software NEX-6 v1.01 Ascii False DateTime 2015:05:24 10:46:23 Short False YCbCrPositioning 2 Undefined True Unknown {80, 114, 105, 110...} Rational False ExposureTime 0.01 Rational False FNumber 13 Short False ExposureProgram 2 Short False ISOSpeedRatings 100 (以下略) PS C:\temp> $image.GetExifProfile().Values.Where({$_.Tag -eq 'FocalLengthIn35mmFilm'}).Value 24
1ファイル系の処理
1ファイルに対する処理をこの画像を使って行っていく。回転(変換と保存)
回転はRotate()。PS c:\temp> $image = New-Object ImageMagick.MagickImage c:\temp\sample.jpg PS C:\temp> $image.Rotate(90) PS C:\temp> $image.Write('c:\temp\sample-out.jpg')
(多分)EXIFにしたがって回転してくれるAutoOrient()ってのもある。便利ね。
切り出し
切り出しはCrop()。Gravityはどこからひっぱってくるかの指示。PS c:\temp> $image = New-Object ImageMagick.MagickImage c:\temp\sample.jpg PS c:\temp> $image.Crop(200, 200, [ImageMagick.Gravity]::Center) PS C:\temp> $image.Write('c:\temp\sample-out.jpg')
リサイズ
Resize()かResample()かScale()かThumbnail()。この使い分けは難しいのだけど、とりあえず無難そうなResizeだけ説明。そのResize()もパラメータをいくつか選べる。
PS C:\temp> $image.Resize OverloadDefinitions ------------------- void Resize(int width, int height) void Resize(ImageMagick.MagickGeometry geometry) void Resize(ImageMagick.Percentage percentage) void Resize(ImageMagick.Percentage percentageWidth, ImageMagick.Percentage percentageHeight)
一番簡単なやつがwidth, heightを指定するやつ(New-ObjectとWriteはここの説明では省略)
PS C:\temp> $image.Resize(200, 100)ただし、アスペクト比を維持するので、(200, 100)にはならない。ここに入る大きさになる。
パーセンテージ指定が次に簡単(書き方がイマイチ...)
PS C:\temp> image.Resize((New-Object ImageMagick.Percentage 50))
で、もっともいろいろできるのが、MagickGeometry指定パターン。ImageMagickをコマンドで使っている人ならおなじみの、例の記法が使える。例えば、アスペクト比を無視する!を付与したスタイル。
PS C:\temp> $image.Resize((New-Object ImageMagick.MagickGeometry '200x200!'))
このあたりの挙動はImageMagickそのものの話なので、詳しくは公式ドキュメント(ImageMagick v6 Examples --
Resize or Scaling (General Techniques))を参照。なお、日本語でまとめてくれてる人もいる(ImageMagickでリサイズする方法)
複数ファイル系の処理
ここからはこの2つの画像を例に説明。結合
結合は、MagickImageCollectionを作ってimageを追加し、AppendHorizontally()かAppendVertically()を呼ぶ。PS C:\temp> $image1 = New-Object ImageMagick.MagickImage c:\temp\sample.jpg PS C:\temp> $image2 = New-Object ImageMagick.MagickImage c:\temp\sample2.jpg PS C:\temp> $col = New-Object ImageMagick.MagickImageCollection PS C:\temp> $col.Add($image1) PS C:\temp> $col.Add($image2) PS C:\temp> $image = $col.AppendVertically() # あるいは $col.AppendHorizontally() PS C:\temp> $image.Write('c:\temp\sample-out.jpg')
重ねる
結合とだいたい同じノリだけども、重ねることも可能。PS C:\temp> $image1 = New-Object ImageMagick.MagickImage c:\temp\sample.jpg PS C:\temp> $image2 = New-Object ImageMagick.MagickImage c:\temp\sample2.jpg PS C:\temp> $col = New-Object ImageMagick.MagickImageCollection PS C:\temp> $col.Add($image1) PS C:\temp> $col.Add($image2) PS C:\temp> $image = $col.Evaluate([ImageMagick.EvaluateOperator]::Add) PS C:\temp> $image.Write('c:\temp\sample-out.jpg')
ここまで書いておいて例が悪いな。
etc, etc...
あとはBlurとか、さまざまなフィルタがあるのだけど、きりないからこのへんでやめ。ImageMagickは加工は得意なので、できることはたくさんある。他のものを知りたかったら、$image.で[TAB]してみたり、MagickNetのドキュメントとか、ImageMagickのコマンドラインオプション(ImageMagick: Command-line Options)を参照。
おまけ: 統計情報の表示
最後に一応、需要はなさそうだけど、これをやるために使い始めたので説明。平均とかの取り方。Statistics()を呼んで、Composite()あるいはGetChannel(チャンネル)を呼ぶ。
先に
$image.Grayscale([ImageMagick.PixelIntensityMethod]::Lightness)を呼んでおくのも便利かも。
PS C:\temp> $stat = $image.Statistics() PS C:\temp> $stat.Composite() Channel : Composite Depth : 1 Entropy : 0.966856332282486 Kurtosis : -1.25816857805541 Maximum : 65535 Mean : 27115.482213115 Minimum : 0 Skewness : 0.0779930273889325 StandardDeviation : 17685.6919467067 Sum : 27115.482213115 SumCubed : 45922010656938.6 SumFourthPower : 2.14361048522477E+18 SumSquared : 1049386363.99743 Variance : 312783699.633806 PS C:\temp> $stat.GetChannel([ImageMagick.PixelChannel]::Blue) Channel : Blue Depth : 8 Entropy : 0.967346949241304 Kurtosis : -1.26933689965117 Maximum : 65535 Mean : 28404.2418122061 Minimum : 0 Skewness : 0.0995661483530123 StandardDeviation : 18559.3929293196 Sum : 28404.2418122061 SumCubed : 52904690206274 SumFourthPower : 2.59600361552516E+18 SumSquared : 1151252018.83115 Variance : 1151252018.83115
Springありで@TheoryしたいときはSpringClassRule/SpringMethodRuleを使う
タイトルの通り。
@RunWith(SpringJUnit4ClassRunner.class) を指定していると JUnit4 で @Theory とか @Parameterized したいときに困るわけだけども、SpringClassRule と SpringMethodRule を使えば、RunWithにTheories.classとかを指定できる。
サンプル的なものはこちら。
参考:Spring Framework Reference Documentation 14.5.8 TestContext Framework support classes
比較的新しいらしい@FromDataPointsも使ってみたよ。
なお、Spring 4.2からのようなので注意。
@RunWith(SpringJUnit4ClassRunner.class) を指定していると JUnit4 で @Theory とか @Parameterized したいときに困るわけだけども、SpringClassRule と SpringMethodRule を使えば、RunWithにTheories.classとかを指定できる。
サンプル的なものはこちら。
参考:Spring Framework Reference Documentation 14.5.8 TestContext Framework support classes
比較的新しいらしい@FromDataPointsも使ってみたよ。
なお、Spring 4.2からのようなので注意。
testCompile 'org.springframework:spring-test:4.2.0.RELEASE'
2015-08-08
Caliburn.Micro + AutofacでWPFプロジェクトを開始する方法のメモ
ひとつひとつ解説すると長くなるので、使い慣れた構成で淡々とプロジェクトを開始できるようにというメモにする(更新するかも)
(フォルダは切らなくてもいい: View / View Model Naming Conventionsを参照)
ShellViewModelはCaliburn.Micro.PropertyChangedBaseを継承し、Component attributeを付与する(Scope=ComponentScope.SingleInstanceにするかは好みで)
ShellView.csのクラスにも[Component]をつける。
(つけなくてもいいが、[Resource]によるフィールド・インジェクションが可能になるので、これによってビュー向けのシングルトンインスタンスを受け取ると便利。)
Customizing The Bootstrapper を参照。Caliburn.Micro.AutofacBootstrap がそこそこやってくれるのだけど、ちょっとばかり微妙なので、Autofacへのつなぎは以下で済ませる。
ShellView.xaml追記
ShellViewModel.cs
概要
以下の構成でプロジェクトを開始する。- Caliburn.Micro (MVVMライブラリ) → ガイド
- Autofac (DIコンテナ) → ガイド
- Autofac.AttributedComponent (Autofacへのコンポーネント収集)
- ReactiveProperty + Rx(Model/ViewModel間の連携)
プロジェクトの作成とパッケージインストール
- WPFプロジェクトを作る
- 以下をInstall-Package
- Caliburn.Micro
- Autofac.AttributedComponent
- ReactiveProperty
初期View / ViewModelの作成
ViewModels/ShellViewModel.csにViewModelを作成、Views/ShellView.xamlにWindowを作成。(フォルダは切らなくてもいい: View / View Model Naming Conventionsを参照)
ShellViewModelはCaliburn.Micro.PropertyChangedBaseを継承し、Component attributeを付与する(Scope=ComponentScope.SingleInstanceにするかは好みで)
ShellView.csのクラスにも[Component]をつける。
(つけなくてもいいが、[Resource]によるフィールド・インジェクションが可能になるので、これによってビュー向けのシングルトンインスタンスを受け取ると便利。)
AppBootstrapperの作成
Caliburn.Micro初期化用クラスをBootstrapperBaseを継承して作る。Customizing The Bootstrapper を参照。Caliburn.Micro.AutofacBootstrap がそこそこやってくれるのだけど、ちょっとばかり微妙なので、Autofacへのつなぎは以下で済ませる。
App.xamlの設定
以下のようにする。StartupUriは削除する。MainWindow.xaml(.cs)の削除
いらないので削除。おまけ: お試し
試したかったら、まぁこんな感じで(Basic Configuration, Actions and Conventionsより)ShellView.xaml追記
ShellViewModel.cs
2015-08-04
groovyshでJavaのクラス情報を参照して適当にコード片を生成する
IDEがやってくれるような一般的なコード生成ではないが、規則のあるコードをちょちょいと生成したりしたいときがある。Annotation Processingでアノテーション見ながらというのが王道だろうが、そこまででもない使い捨てな感じにコードをGroovyを使って生成する方法。
単純なケースであれば、クラスパスを指定して、groovyshを起動する。
普通はさらさらっと指定できるほど単純ではないと思うが、Gradleからgradle-groovysh-pluginを使ってgroovyshを起動すれば、必要なものをロードしておいてくれる。
基本的には、以下の変更をbuild.gradleに加えた後「./gradlew -q --no-daemon shell」で起動すればOK。
ただし、gradle.propertiesに「org.gradle.daemon=true」が書いてあると「Do not run with gradle daemon (use --no-daemon)」と言われて起動できない。--no-daemonをつけろと言うのだが、つけてもダメ。多分チェック方法が間違っている。Issueに登録しておいたが、直るまでは実行するときはファイルにtrueを記述していない状態にするしかない。
[2015/8/26追記] warningが出つつも起動するようにしてもらえたので、ちゃんと-q --no-daemonで起動すればOK。
たとえば、こんな感じ。
ポイントとしては、import なんちゃらと打つとパッケージ名の補完が効くので、いったんimportしちゃうなり、補完までしてimportを書き換えるなどするとパッケージ/クラス名の入力が楽。
しかし、GroovyのmetaClassを見ると、setter/getterはset/getを外した形でpropertiesに入っているので、こっちを使うとかなり楽になる(classもあるけど、そこはうまいこと外す)
もし役に立つ機会があればどうぞ。
groovyshの起動
GroovyのREPLであるところのgroovyshをまず起動する。追記: なお、IntelliJ IDEAであればTools > Groovy Consoleで起動できるGroovyで処理できるので、いきなり次の作業に飛んで問題ない。単純なケースであれば、クラスパスを指定して、groovyshを起動する。
$ groovysh -cp out/production/javaclasstest
普通はさらさらっと指定できるほど単純ではないと思うが、Gradleからgradle-groovysh-pluginを使ってgroovyshを起動すれば、必要なものをロードしておいてくれる。
基本的には、以下の変更をbuild.gradleに加えた後「./gradlew -q --no-daemon shell」で起動すればOK。
- buildscriptのdependenciesにcom.tkruse.gradle:gradle-groovysh-pluginを追加する。
- 'java'と'com.github.tkruse.groovysh'をapply pluginする。
apply plugin: 'java' apply plugin: 'com.github.tkruse.groovysh' buildscript { repositories { jcenter() } dependencies { classpath 'com.tkruse.gradle:gradle-groovysh-plugin:1.0.7' } }
ただし、gradle.propertiesに「org.gradle.daemon=true」が書いてあると「Do not run with gradle daemon (use --no-daemon)」と言われて起動できない。--no-daemonをつけろと言うのだが、つけてもダメ。多分チェック方法が間違っている。Issueに登録しておいたが、直るまでは実行するときはファイルにtrueを記述していない状態にするしかない。
[2015/8/26追記] warningが出つつも起動するようにしてもらえたので、ちゃんと-q --no-daemonで起動すればOK。
リフレクションを使って適当に作る
あとはjava.lang.Classやjava.lang.reflect.Methodを見ながら、適当にコードを生成する。GroovyなのでgetHogeはhogeでいいし、Rubyちっくに適当につなげれば生成できるのでラク。たとえば、こんな感じ。
$ ./gradlew shell -q (略) groovy:000> cls = com.example.HelloJavaBuilder ===> class com.example.HelloJavaBuilder groovy:000> cls.methods.findAll { it.declaringClass == cls }.collect { ".${it.name}()" }.sort().join("\n") ===> .build() .fuga() .hige() .hoge() groovy:000>
ポイントとしては、import なんちゃらと打つとパッケージ名の補完が効くので、いったんimportしちゃうなり、補完までしてimportを書き換えるなどするとパッケージ/クラス名の入力が楽。
Tips: GroovyのMetaClassを活用する
setterのsetを外したところを取りたい、みたいな話だと、普通にやるとこんな感じだと思う。groovy:000> cls.methods.findAll { it.declaringClass == cls && it.name =~ /set/ }.sort(). collect { it.name.replaceAll(/set/, '').replaceAll(/^./) { it.toLowerCase() } }
しかし、GroovyのmetaClassを見ると、setter/getterはset/getを外した形でpropertiesに入っているので、こっちを使うとかなり楽になる(classもあるけど、そこはうまいこと外す)
groovy:000> cls.metaClass.properties.collect{ it.name }
もし役に立つ機会があればどうぞ。