Cocoaはやっぱり!
JPEG/PNGで保存
今回のテーマ

画像処理したらやっぱりそれを保存したいですよね。ということで、今回はJPEG圧縮して保存というのを説明したいと思います。

推奨環境 この解説は、以下の環境を前提に作成し、動作確認等を行っています。ご確認ください。

改版履歴

JPEGでの保存

NSBitmapImageRepは、自分が持っている生のビットマップデータを指定された画像のデータにエンコードして出力するメソッドを持っています。それが、representationUsingType: properties : メソッドです。

NSBitmapImageRep : 指定の画像にエンコードしたデータを作る
書式 : - (NSData *) representationUsingType : (NSBitmapImageFileType) type    : properties : (NSDictionary *) properties 入力 : type - 画像フォーマット    : properties - エンコード時の辞書形式でのプロパティ指定 出力 : RETURN - 画像の生データ

NSBitmapImageFileTypeは画像フォーマットの指定を行います。ヘッダーを見ますと以下のようになっています。このうちのJPEGを今回は使います。

NSBitmapImageFileType : 画像フォーマット種別
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を与えます。

JPEGで圧縮率を指定するNSDictionary
NSDictionary *propJpeg; propJpeg = [ NSDictionary dictionaryWithObjectsAndKeys : [ NSNumber numberWithFloat : quality ], NSImageCompressionFactor, nil ];

プロパティが出来たら、「 representationUsingType: properties : 」を使ってエンコードを行います。これによって生成されたNSDataは writeToFile : atomically : を使ってファイルに書き出します。

NSData : データをファイルに書き出す
書式 : writeToFile : (NSString *) path    : atomically : (BOOL ) flag 入力 : path - 出力先のファイルのパス    : flag - YESならバックアップファイルに書き出してからリネームする。NOなら直接指定ファイルに書き出す。 出力 : RETURN - 成功したら (=YES)、失敗したら (=NO)

ということで以下のようなコードになります。これもカテゴリとして実装します。NSBitmapImageRepが拡張されてより使いやすくなります。

PixelAccess.m > saveAsJpeg: quality:
- (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のときと大体同じと思っていいと思います。

PixelUtil.m > saveAsJpeg
- (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というクラスメソッドを使ってオブジェクトを生成します。

NSSavePanel : ファイル保存のパネルを表示する
書式 : - (int) runModalForDirectory : (NSString *) path    : path file : (NSString *) name 入力 : path - 表示するフォルダーのパス    : name - 出力するファイル名のデフォルト値。指定しない時は@""を指定。 出力 : RETURN - 決定時 - NSOKButton、キャンセル時 - NSCancelButton

ついでにPNGでの保存のサンプルも書いておきます。PNGの場合は、指定できるプロパティはインターレースにするかしないかだけで、これをNSImageInterlacedをキーとしてBOOL値で指定します。PNGファイルはアルファチャンネルを持つことが出来ますので、アルファチャンネルを削る処理は必要ありません。

レスポンス取得通知 : MyClass.m
- (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 ); }