画像のサムネイル作成関数について

665 views
Skip to first unread message

yukio.m...@gmail.com

unread,
Dec 10, 2008, 9:25:00 AM12/10/08
to cocoa-dev-japan
こんにちは、村上です。
デジカメで作成したJPEG画像のサムネイル(小さいサイズのJPEG画像)を生成する関数を作成しようと考えています。
汎用性(将来、iPhoneアプリで利用できるかもしれませんので)を考え、Quartzでの実装を考え、以下のコードを記述したのですが、思うとお
り、小さいサイズの画像にはなりませんでした。
何かを勘違いしているのだと思いますが、指摘していただけますと嬉しいです。

if (UTTypeConformsTo((CFStringRef)uti, kUTTypeJPEG)) {
NSURL *sourceUrl = [NSURL fileURLWithPath:filePath];
CGImageRef imageRef = NULL;
CGImageSourceRef sourceRef;
sourceRef = CGImageSourceCreateWithURL((CFURLRef)sourceUrl, NULL);
if (sourceRef) {
imageRef = CGImageSourceCreateImageAtIndex(sourceRef, 0, NULL);
CFRelease(sourceRef);
}

NSURL *destUrl = [NSURL fileURLWithPath:thumbnailPath];
CGImageDestinationRef imageDestination=
CGImageDestinationCreateWithURL((CFURLRef)destUrl, kUTTypeJPEG, 1,
NULL);

float width = 320.0, heignt = 240.0;
CFTypeRef keys[2];
CFTypeRef values[2];
CFDictionaryRef options = NULL;
keys[0] = kCGImagePropertyDPIWidth;
keys[1] = kCGImagePropertyDPIHeight;
values[0] = CFNumberCreate(NULL, kCFNumberFloatType, &width);
values[1] = CFNumberCreate(NULL, kCFNumberFloatType, &heignt);
options = CFDictionaryCreate(NULL, (const void **)keys, (const void
**)values, 2,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFRelease(values[0]);
CFRelease(values[1]);
CGImageDestinationAddImage(imageDestination, imageRef, options);
CFRelease(options);
CGImageDestinationFinalize(imageDestination);
CFRelease(imageDestination);
}

narumi

unread,
Dec 10, 2008, 10:37:03 AM12/10/08
to cocoa-dev-japan
眺めてて気付いたのですが
kCGImagePropertyDPIWidthとkCGImagePropertyDPIHeightは解像度のキーで、1インチあたりのピクセル
数でしたっけ。
サイズの指定はkCGImagePropertyPixelWidthとkCGImagePropertyPixelHeightだと思います。

このコードでは、元画像が仮に72dpiで、720x720pixe.の画像だった場合、3200x2400pixelの画像になると予想してます。
その辺の挙動が実際と違うようだと、僕の予想も全然はずれてるかもしれません。

MIYAZAKI Yasushi

unread,
Dec 10, 2008, 11:21:28 AM12/10/08
to cocoa-d...@googlegroups.com
みやざきです。

参考にならないかもしれませんが、サムネイル用のAPIがある
ので、使ってみました。
# iPhoneじゃダメかも。
ローカルのファイル名で第一引数に元画像ファイル名、
第二引数にサムネイル後のファイル名を指定してください。
Frameworkには ApplicationService.Frameworkと
CoreFoundation.Frameworkを指定すれば動きます。


#include <CoreFoundation/CoreFoundation.h>
#include <ApplicationServices/ApplicationServices.h>

extern int workProcess(char *, char *);

int main (int argc, const char * argv[]) {
workProcess((char *)argv[1], (char *)argv[2]);
return 0;
}

int workProcess (char * src, char * dst) {
CFStringRef srcStrRef;
CFStringRef dstStrRef;
CFURLRef srcUrl;
CFURLRef dstUrl;
CGImageRef imageRef = NULL;
CGImageSourceRef srcRef;
CGImageDestinationRef dstRef;
long maxSize = 320;
float quality = 0.90;
CFTypeRef keys[1];
CFTypeRef values[1];
CFDictionaryRef options = NULL;

srcStrRef = CFStringCreateWithCString(NULL, src,
kCFStringEncodingUTF8);
srcUrl = CFURLCreateWithFileSystemPath(NULL, srcStrRef,
kCFURLPOSIXPathStyle, false);
CFRelease(srcStrRef);
srcRef = CGImageSourceCreateWithURL(srcUrl, NULL);
CFRelease(srcUrl);

keys[0] = kCGImageSourceThumbnailMaxPixelSize;
values[0] = CFNumberCreate(NULL, kCFNumberLongType, &maxSize);
options = CFDictionaryCreate(NULL, (const void **)keys, (const void
**)values, 1, &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFRelease(values[0]);
imageRef = CGImageSourceCreateThumbnailAtIndex(srcRef, 0, options);
CFRelease(srcRef);
CFRelease(options);

dstStrRef = CFStringCreateWithCString(NULL, dst,
kCFStringEncodingUTF8);
dstUrl = CFURLCreateWithFileSystemPath(NULL, dstStrRef,
kCFURLPOSIXPathStyle, false);
CFRelease(dstStrRef);
dstRef = CGImageDestinationCreateWithURL(dstUrl, kUTTypeJPEG, 1, NULL);
CFRelease(dstUrl);

keys[0] = kCGImageDestinationLossyCompressionQuality;
values[0] = CFNumberCreate(NULL, kCFNumberFloatType, &quality);
options = CFDictionaryCreate(NULL, (const void **)keys, (const void
**)values, 1, &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFRelease(values[0]);
CGImageDestinationAddImage(dstRef, imageRef, options);
CFRelease(imageRef);
CFRelease(options);

CGImageDestinationFinalize(dstRef);
CFRelease(dstRef);

return 0;
}




On 2008/12/10, at 23:25, yukio.m...@gmail.com wrote:

>
> こんにちは、村上です。
> デジカメで作成したJPEG画像のサムネイル(小さいサイズのJPEG
> 画像)を生成する関数を作成しようと考えています。
> 汎用性(将来、iPhoneアプリで利用できるかもしれません
> ので)を考え、Quartzでの実装を考え、以下のコードを記
> 述したのですが、思うとお
> り、小さいサイズの画像にはなりませんでした。
> 何かを勘違いしているのだと思いますが、指摘していただけます
MIYAZAKI Yasushi
MIYAZAKI...@gmail.com



慧 松本

unread,
Dec 10, 2008, 4:58:41 PM12/10/08
to cocoa-d...@googlegroups.com
松本です。

On 2008/12/10, at 23:25, yukio.m...@gmail.com wrote:

> デジカメで作成したJPEG画像のサムネイル(小さいサイズのJPEG
> 画像)を生成する関数を作成しようと考えています。

読み込みと保存の部分がありませんが、image のサイズ変更
は以下の関数でできます。
参考になるでしょうか?

void resizeImage( NSImage *anImage, NSSize newSize )
{
NSImage *workImage = [[NSImage alloc] initWithSize:newSize];

NSSize oldSize = [anImage size];
NSRect sourceRect = NSMakeRect(0, 0, oldSize.width,
oldSize.height);
NSRect destRect = NSMakeRect(0, 0, newSize.width, newSize.height);

[anImage setScalesWhenResized:YES];
[anImage setSize:newSize];

[workImage lockFocus];
[anImage drawInRect:destRect fromRect:sourceRect
operation:NSCompositeCopy fraction:1.0];
[workImage unlockFocus];

[workImage release];

}

-----------------------------------------------------
Satoshi Matsumoto <sat...@mac.com>
816-5 Odake, Odawara, Kanagawa, Japan 256-0802


yukio.m...@gmail.com

unread,
Dec 12, 2008, 8:38:48 PM12/12/08
to cocoa-dev-japan
> サイズの指定はkCGImagePropertyPixelWidthとkCGImagePropertyPixelHeightだと思います。

これは、試したみたのですが、何故か、うまくいきませんでした。
何かに気がついていないような気がするのですが。

yukio.m...@gmail.com

unread,
Dec 13, 2008, 2:48:12 AM12/13/08
to cocoa-dev-japan
CGImageSourceRefからCGImageRefを作成する際に、CGImageSourceCreateImageAtIndex()でな
く、サムネイルを作成するCGImageSourceCreateThumbnailAtIndexを作成すれば、いいのですね。
うまく行きました
そして、何故、私のコードではうまくいかなかったのかのヒントになりました。
ありがとうございます。

yukio.m...@gmail.com

unread,
Dec 13, 2008, 3:41:20 AM12/13/08
to cocoa-dev-japan
NSImageの利用ですが、初めよく分からなかったので選択しなかったというのもあります。
以下のコードを書いていたのですが、駄目でした。

NSImage *image = [[NSImage alloc] initWithContentsOfFile:filePath];
[image setSize:NSMakeSize(128.0, 96.0)];
NSBitmapImageRep *bitmapRep = [NSBitmapImageRep imageRepWithData:
[image TIFFRepresentation]];
[bitmapRep setAlpha:NO];
float quality = 1.0f;
NSDictionary *properties = [NSDictionary dictionaryWithObject:
[NSNumber numberWithFloat:quality]
forKey:NSImageCompressionFactor];
NSData *data = [bitmapRep representationUsingType:NSJPEGFileType
properties:properties];
NSString *thumbnailPath = [thumbnails stringByAppendingString:@"/"];
thumbnailPath = [thumbnailPath stringByAppendingString:file];
NSLog(@"%@", thumbnailPath);
[data writeToFile:thumbnailPath atomically:YES];
[image release];

松本さんのコードは、作業用NSImageに変更したいサイズで描画するのですね。
ただ、申し訳ございませんが、まだ、よく理解できない部分があります。

anImageを上記のコードの方法で、TIFFデータを取り出し、保存しましたが、小さいサイズになりませんでした。
workImageにあるのが、リサイズしたデータと考え、これのTIFFデータを取り出し、保存しましたが、望みのサイズでしたが、真っ黒でした。
以下が、そのコードなのですが、勘違いしている箇所を指摘していただけると、嬉しいです。

NSImage *image = [[NSImage alloc] initWithContentsOfFile:filePath];
NSImage *workImage = [[NSImage alloc] initWithSize:NSMakeSize(128.0,
96.0)];
NSSize oldSize = [image size];
NSRect sourceRect = NSMakeRect(0, 0, oldSize.width, oldSize.height);
NSRect destRect = NSMakeRect(0, 0, 128, 96);
[image setScalesWhenResized:YES];
[image setSize:NSMakeSize(128.0, 96.0)];
[workImage lockFocus];
[image drawInRect:destRect fromRect:sourceRect
operation:NSCompositeCopy fraction:1.0];
[workImage unlockFocus];
[image release];

NSBitmapImageRep *bitmapRep = [NSBitmapImageRep imageRepWithData:
[workImage TIFFRepresentation]];
[bitmapRep setAlpha:NO];
float quality = 1.0f;
NSDictionary *properties = [NSDictionary dictionaryWithObject:
[NSNumber numberWithFloat:quality]
forKey:NSImageCompressionFactor];
NSData *data = [bitmapRep representationUsingType:NSJPEGFileType
properties:properties];
NSString *thumbnailPath = [thumbnails stringByAppendingString:@"/"];
thumbnailPath = [thumbnailPath stringByAppendingString:file];
NSLog(@"%@", thumbnailPath);
[data writeToFile:thumbnailPath atomically:YES];
[workImage release];

慧 松本

unread,
Dec 13, 2008, 5:58:03 AM12/13/08
to cocoa-d...@googlegroups.com
松本です。

拙作のエディタに大きすぎるイメージがドロップされたときにそれ
をページ幅に縮小
するときにイメージのリサイズをします。

いま、コードをみたらどうも私もへんだと思って、エディタのソー
スを調べてみたら
実際に使っているコードは以下のようになっていました。

NSImage *ResizedImage( NSImage *anImage, NSSize newSize )
{
NSImage *workImage = [[NSImage alloc] initWithSize:newSize];

NSSize oldSize = [anImage size];
NSRect sourceRect = NSMakeRect(0, 0, oldSize.width,
oldSize.height);
NSRect destRect = NSMakeRect(0, 0, newSize.width, newSize.height);

[workImage lockFocus];
[anImage drawInRect:destRect fromRect:sourceRect
operation:NSCompositeCopy fraction:1.0];
[workImage unlockFocus];

[workImage autorelease];

  return workImage;
}


On 2008/12/13, at 17:41, yukio.m...@gmail.com wrote:

>
> NSImageの利用ですが、初めよく分からなかったので選択しなかっ

yukio.m...@gmail.com

unread,
Dec 13, 2008, 8:50:19 AM12/13/08
to cocoa-dev-japan
ありがとうございます。うまく行きました。
私の先ほどのコードで

[image setScalesWhenResized:YES];
[image setSize:NSMakeSize(128.0, 96.0)];

とした為、元の画像が小さくなったのに、元のサイズで縮小描画
した為、非常に小さくなっていました。

上記の2行を残したまま、描画を

//[image drawInRect:destRect fromRect:sourceRect
// operation:NSCompositeCopy fraction:1.0];
[image drawInRect:destRect fromRect:NSMakeRect(0, 0, 128.0, 96.0)
operation:NSCompositeCopy fraction:1.0];

としたら、これでも上手く行きました。

narumi

unread,
Dec 13, 2008, 11:49:55 AM12/13/08
to cocoa-dev-japan
Quartzで試してみました。

UTTypeはわからないのでコメントアウトしました。

void MakeAndSaveThumbNail(NSString *filePath, NSString *thumbnailPath)
{
// if (UTTypeConformsTo((CFStringRef)uti, kUTTypeJPEG)) {
{
NSURL *sourceUrl = [NSURL fileURLWithPath:filePath];
CGImageRef imageRef = NULL;

CGImageRef imageRef2 = NULL;

CGImageSourceRef sourceRef;
sourceRef = CGImageSourceCreateWithURL((CFURLRef)sourceUrl, NULL);
if (sourceRef) {
imageRef = CGImageSourceCreateImageAtIndex(sourceRef, 0, NULL);
CFRelease(sourceRef);
}

CGFloat size = 160.0;
CGFloat imageWidth,imageHeight;
CGFloat newWidth,newHeight;
imageWidth = CGImageGetWidth(imageRef);
imageHeight = CGImageGetHeight(imageRef);
newWidth = fmin(size, size * (imageWidth / imageHeight) );
newHeight = fmin(size, size * (imageHeight / imageWidth) );
CGContextRef bitmapContext = CGBitmapContextCreate(NULL, newWidth,
newHeight, 8, 8*3*newWidth, CGColorSpaceCreateDeviceRGB(),
kCGImageAlphaPremultipliedLast);
CGContextDrawImage(bitmapContext, CGRectMake
(0,0,newWidth,newHeight), imageRef);
imageRef2 = CGBitmapContextCreateImage(bitmapContext);

NSURL *destUrl = [NSURL fileURLWithPath:thumbnailPath];
CGImageDestinationRef imageDestination=
CGImageDestinationCreateWithURL((CFURLRef)destUrl, kUTTypeJPEG, 1,
NULL);
float resolution = 72.0;
CFTypeRef keys[2];
CFTypeRef values[2];
CFDictionaryRef options = NULL;
keys[0] = kCGImagePropertyDPIWidth;
keys[1] = kCGImagePropertyDPIHeight;
values[0] = CFNumberCreate(NULL, kCFNumberFloatType, &resolution);
values[1] = CFNumberCreate(NULL, kCFNumberFloatType, &resolution);
options = CFDictionaryCreate(NULL,
(const void **)keys,
(const void **)values,
2,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFRelease(values[0]);
CFRelease(values[1]);
CGImageDestinationAddImage(imageDestination, imageRef2, options);
CFRelease(options);
CGImageDestinationFinalize(imageDestination);
CFRelease(imageDestination);

CFRelease(imageRef);
CFRelease(imageRef2);
CFRelease(bitmapContext);
}
}

yukio.m...@gmail.com

unread,
Dec 14, 2008, 9:50:28 AM12/14/08
to cocoa-dev-japan
なるほど、これはNSImageの方法と、同様な手法ですね。
NSImageでは、作業用NSImageに描画して、サイズを変更しましたが、描画用ビットマップに描画して、それを保存するのですね。

narumi

unread,
Dec 14, 2008, 4:45:46 PM12/14/08
to cocoa-dev-japan
そうですね。結局この方法になってしまいました。

最初安易に考えてしまって、ウソ答えてしまってすんません。

ちょっと発見だったのは、リファレンスちょくちょくみながらソース書いたのですが、
ビットマップコンテキストって必ずmallocしたメモリのブロックが必要なのかと思ってたのですが
10.4からメモリブロックへのアクセスが必要ない場合、NULLを渡すとQuartzの側がメモリ管理してくれるらしく
凄く楽だなと。

あと、この縮小方法だと画像がかなりぼけてしまって気に入りませんね。
CIImageとかつかってすこしエッジを付けてあげないと。
そーするとマックでしか動かないから意味ないかもですね。

narumi

unread,
Dec 14, 2008, 4:53:55 PM12/14/08
to cocoa-dev-japan
すいません、間違いを重ねてしまいました。
CGBitmapContextGetDataでvoid *とれるんで、
”コンテキストリリースした場合にデータもなくなって良いケースでは、自分でメモリ管理しなくていい” ですね。

慧 松本

unread,
Jul 31, 2009, 3:50:47 AM7/31/09
to cocoa-d...@googlegroups.com
松本です。

Finder の情報パネル「このアプリケーションで開く」で、そのファ
イルをダブルクリックしたときに起動されるアプリケーションを参
照したり変更したりできます。

Cocoa からは、NSWorkspace の以下のメソッド

- (BOOL)getInfoForFile:(NSString *)fullPath application:(NSString
**)appName type:(NSString **)type;

で、起動されるアプリケーションを参照することはできます。

しかし、この起動アプリケーションをCocoaから変更する方法
がわかりません。

どなたかご存知でしたら教えてもらえるとうれしいです。

wang

unread,
Jul 31, 2009, 10:39:33 AM7/31/09
to cocoa-dev-japan
~/Library/Preferences/com.apple.LaunchServices.plist

に記録されているのでこれを直接編集すれば変更できますが、そういうことじゃないですよね。
CarbonのLaunchServecesで編集するAPIがあるかも知れませんが、こちらは使ったことがありません。

MATSUMOTO Satoshi

unread,
Jul 31, 2009, 5:39:32 PM7/31/09
to cocoa-d...@googlegroups.com
松本です。

On 2009/07/31, at 23:39, wang wrote:
> ~/Library/Preferences/com.apple.LaunchServices.plist
> に記録されているのでこれを直接編集すれば変更できますが、そ
> ういうことじゃないですよね。
> CarbonのLaunchServecesで編集するAPIがあるかも知
> れませんが、こちらは使ったことがありません。


LaunchServecesのProgramming Guideを読むと特定のファイル
について、ダブルクリックしたときに起動するアプリケーションのことを
User-Specified Binding Preferences
http://developer.apple.com/documentation/Carbon/Conceptual/LaunchServicesConcepts/LSCConcepts/LSCConcepts.html#/
/apple_ref/doc/uid/TP30000999-CH202-DontLinkElementID_8
というらしいです。

それを LSGetApplicationForItem で参照することはできるの
ですが、プログラムから設定する方法がわかりません。

特定のファイルについてではなく、特定のタイプ(UTI)には以
下で起動アプリケーションを設定できそうなのですが。
OSStatus LSSetDefaultRoleHandlerForContentType ( CFStringRef
inContentType, LSRolesMask inRole, CFStringRef inHandlerBundleID );

うーむ。

wang

unread,
Jul 31, 2009, 10:58:41 PM7/31/09
to cocoa-dev-japan
あ、そっちの話でしたか。失礼しました。

コマンドラインからはls -l@で参照できるファイルごとの拡張情報の中に入っています。
xattr -l で観ると、
com.apple.FinderInfoのなかになにかフラグ情報が含まれていて実際にどのアプリから参照されるかは
com.apple.ResourceForke中に含まれているようです。

これらの情報をプログラムからどのように参照すればいいのかはわかりません。

FinderにApple Eventとか送ればなんとかなるのかなあ?

On 8月1日, 午前6:39, MATSUMOTO Satoshi <sato...@mac.com> wrote:
> 松本です。
>
> On 2009/07/31, at 23:39, wang wrote:
>
> > ~/Library/Preferences/com.apple.LaunchServices.plist
> > に記録されているのでこれを直接編集すれば変更できますが、そ
> > ういうことじゃないですよね。
> > CarbonのLaunchServecesで編集するAPIがあるかも知
> > れませんが、こちらは使ったことがありません。
>
> LaunchServecesのProgramming Guideを読むと特定のファイル
> について、ダブルクリックしたときに起動するアプリケーションのことを
> User-Specified Binding Preferenceshttp://developer.apple.com/documentation/Carbon/Conceptual/LaunchServ...
> というらしいです。
>
> それを LSGetApplicationForItem で参照することはできるの
> ですが、プログラムから設定する方法がわかりません。
>
> 特定のファイルについてではなく、特定のタイプ(UTI)には以
> 下で起動アプリケーションを設定できそうなのですが。
> OSStatus LSSetDefaultRoleHandlerForContentType ( CFStringRef
> inContentType, LSRolesMask inRole, CFStringRef inHandlerBundleID );
>
> うーむ。
> -----------------------------------------------------
> Satoshi Matsumoto <sato...@mac.com>

MATSUMOTO Satoshi

unread,
Aug 1, 2009, 3:53:49 AM8/1/09
to cocoa-d...@googlegroups.com
松本です。

On 2009/08/01, at 11:58, wang wrote:
> コマンドラインからはls -l@で参照できるファイルごとの
> 拡張情報の中に入っています。
> xattr -l で観ると、
> com.apple.FinderInfoのなかになにかフラグ情報が含まれていて
> 実際にどのアプリから参照されるかは
> com.apple.ResourceForke中に含まれているようです。

たしかに、xattr -l で観ると TextEditが起動される
書類には、com.apple.ResourceForkのなかに

0F80 86 00 00 04 04 00 00 00 1B 2F 41 70 70 6C 69 63 ........./
Applic
0F90 61 74 69 6F 6E 73 2F 54 65 78 74 45 64 69 74 2E ations/
TextEdit.
0FA0 61 70 70 00 00 00 00 00 00 00 00 00 00 00 00 00
app.............

が含まれていて、それをFinderの情報パネルで、Jedit
X(E) で起動するように変更すると

0F80 86 00 00 04 04 00 00 00 26 2F 55 73 65 72 73 2F ........&/
Users/
0F90 73 61 74 6F 73 68 69 2F 44 65 73 6B 74 6F 70 2F satoshi/
Desktop/
0FA0 4A 65 64 69 74 20 58 20 28 45 29 2E 61 70 70 00 Jedit X
(E).app.
0FB0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 ................

のように書き変わりました。

リソースフォークにその書類の起動アプリケーション情報が書かれ
ているようです。

これを、プログラムから変更するのはどのようにしたらいいので
しょうかね。


うーむ。
-----------------------------------------------------
Satoshi Matsumoto <sat...@mac.com>

MATSUMOTO Satoshi

unread,
Aug 2, 2009, 9:05:01 PM8/2/09
to cocoa-d...@googlegroups.com
松本です。

以下のことをプログラムで行うundocumented APIが見つかり
ました。(^^;

これを悪用すると、ユーザーの知らないうちに書類の起動アプリを
別の悪意のあるアプリに書き換えてしまうことができるので、公開
されていないのではないかと考えられます。
Reply all
Reply to author
Forward
0 new messages