d.sunnyone.org
sunnyone.org

ページ

2015-08-23

PdfSharpでPDFファイルからJPEG画像を抽出する

.NET用のPDF操作ライブラリPdfSharpを使って、PDFファイルに含まれるJPEG画像のバイト列を抽出する。iTextではない。

「PdfSharp」NuGetパッケージをインストールして、以下の感じで一応OK。



StreamはStreamって言ってるけどSystem.IO.Streamではないのでバイト列をファイルごとに返すことにした。

参考:PDFsharp Sample: Export Images

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。

準備

インストール

適当なフォルダを作り、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-x64
NuGet的には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からのようなので注意。
testCompile 'org.springframework:spring-test:4.2.0.RELEASE'

2015-08-08

Caliburn.Micro + AutofacでWPFプロジェクトを開始する方法のメモ

ひとつひとつ解説すると長くなるので、使い慣れた構成で淡々とプロジェクトを開始できるようにというメモにする(更新するかも)

概要

以下の構成でプロジェクトを開始する。
  • 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の起動

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.Classjava.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 }

もし役に立つ機会があればどうぞ。