画像処理したらやっぱりそれを保存したいですよね。ということで、今回はJPEG圧縮して保存というのを説明したいと思います。
推奨環境 この解説は、以下の環境を前提に作成し、動作確認等を行っています。ご確認ください。
改版履歴
NSBitmapImageRepは、自分が持っている生のビットマップデータを指定された画像のデータにエンコードして出力するメソッドを持っています。それが、representationUsingType: properties : メソッドです。
書式 : - (NSData *) representationUsingType : (NSBitmapImageFileType) type : properties : (NSDictionary *) properties 入力 : type - 画像フォーマット : properties - エンコード時の辞書形式でのプロパティ指定 出力 : RETURN - 画像の生データ
NSBitmapImageFileTypeは画像フォーマットの指定を行います。ヘッダーを見ますと以下のようになっています。このうちのJPEGを今回は使います。
typedef enum _NSBitmapImageFileType { NSTIFFFileType, // TIFF NSBMPFileType, // BMP NSGIFFileType, // GIF NSJPEGFileType, // JPEG NSPNGFileType, // PNG } NSBitmapImageFileType;
propertiesはエンコード時のオプションですが、JPEGの場合は圧縮率をNSNumberのfloatで指定します。PNGの場合はインターレースにするかどうかをBOOLで指定します。このように画像によって指定できるオプションの種類やデータの種類が異なりますので、プロパティ指定にはNSDictionaryが使われています。
JPEGの圧縮率を指定するNSDictionaryは以下のような記述になります。NSNumberを作って、キーとして圧縮率指定をあらわすNSImageCompressionFactorを与えます。
NSDictionary *propJpeg; propJpeg = [ NSDictionary dictionaryWithObjectsAndKeys : [ NSNumber numberWithFloat : quality ], NSImageCompressionFactor, nil ];
プロパティが出来たら、「 representationUsingType: properties : 」を使ってエンコードを行います。これによって生成されたNSDataは writeToFile : atomically : を使ってファイルに書き出します。
書式 : writeToFile : (NSString *) path : atomically : (BOOL ) flag 入力 : path - 出力先のファイルのパス : flag - YESならバックアップファイルに書き出してからリネームする。NOなら直接指定ファイルに書き出す。 出力 : RETURN - 成功したら (=YES)、失敗したら (=NO)
ということで以下のようなコードになります。これもカテゴリとして実装します。NSBitmapImageRepが拡張されてより使いやすくなります。
- (BOOL) saveAsJpeg : (NSString *) path quality : (float ) quality { NSDictionary *propJpeg; // エンコードプロパティ NSData *dataImg; // JPEGのデータ BOOL bResult; [ self setAlpha : NO ]; // アルファチャンネルを削除 propJpeg = [ NSDictionary dictionaryWithObjectsAndKeys : [ NSNumber numberWithFloat : quality ], NSImageCompressionFactor, nil ]; dataImg = [ self representationUsingType : NSJPEGFileType properties : propJpeg ]; bResult = [ dataImg writeToFile : path atomically : YES ]; return( bResult ); }
アルファチャンネルを削除というところがありますが、これは、Cocoaの使用するJPEGのエンコーダーがNSBitmapImageRepのデータにアルファチャンネルをサポートしていないためで、事前にアルファチャンネルを削除しておく必要があるためです。
保存の処理が出来たので、今度はユーザーインターフェイスを作ります。ウィンドウには「Save as JPEG... 」というボタンを配置して、これによって呼ばれるAction「 saveAsJpeg 」をPixelUtilクラスに追加して接続します。
コードは以下のようになります。NSOpenPanelのときと大体同じと思っていいと思います。
- (IBAction)saveAsJpeg:(id)sender { NSSavePanel *sp = [ NSSavePanel savePanel ]; int iStatus; iStatus = [ sp runModalForDirectory : NSHomeDirectory() file : @"invert.jpg" ]; if ( iStatus == NSOKButton ) { NSBitmapImageRep *bmRep = [ [ [ NSBitmapImageRep alloc ] initWithNSImage : [ vDstImage image ] ] autorelease ]; [ bmRep saveAsJpeg: [ sp filename ] quality : 0.5 ]; } }
NSOpenPanelの代わりにNSSavePanelを使います。savePanelというクラスメソッドを使ってオブジェクトを生成します。
書式 : - (int) runModalForDirectory : (NSString *) path : path file : (NSString *) name 入力 : path - 表示するフォルダーのパス : name - 出力するファイル名のデフォルト値。指定しない時は@""を指定。 出力 : RETURN - 決定時 - NSOKButton、キャンセル時 - NSCancelButton
ついでにPNGでの保存のサンプルも書いておきます。PNGの場合は、指定できるプロパティはインターレースにするかしないかだけで、これをNSImageInterlacedをキーとしてBOOL値で指定します。PNGファイルはアルファチャンネルを持つことが出来ますので、アルファチャンネルを削る処理は必要ありません。
- (BOOL) saveAsPng : (NSString *) path Interlaced : (BOOL) interlaced { NSDictionary *propImg; NSData *dataImg; BOOL bResult; propImg = [ NSDictionary dictionaryWithObjectsAndKeys : [ NSNumber numberWithBool : interlaced ], NSImageInterlaced, nil ]; dataImg = [ self representationUsingType : NSPNGFileType properties : propImg ]; bResult = [ dataImg writeToFile : path atomically : YES ]; return( bResult ); }