OSTRACISM CO.
C#とObjective-CとJavaと...
イメージ処理
GraphicGripGropの最もキモとなる部分は、画像ファイルの読み込みと加工である。画像ファイルのサムネールをこちらの考える仕様で作成する必要がある。
.NET、Cocoa、Javaのいずれも画像を簡単に扱うクラスが用意されている。高機能なグラフィックライブラリというわけでもないので期待は禁物だが、GraphicGripGropで要求する程度のことは可能となっている。
GraphicGripGropは指定された画像ファイルを読み込み、それを8×8のサイズに縮小し、各ピクセルの値を1チャンネル4bitに切り捨てたものをサムネールとして扱う。TFileInfoクラスで処理をする。
C#
public bool MakeThumbnail(int pixcelNumber) {
if (m_filePath == null || m_filePath.Length == 0)
return false;
Bitmap simg = null;
try {
simg = new Bitmap(m_filePath);
}
catch {
}
if (simg == null)
return false;
int height = pixcelNumber;
int width = pixcelNumber;
m_thumbnailCount = height * width;
m_thumbnail = new short[m_thumbnailCount];
Bitmap rimg = new Bitmap(simg, new Size(width, height));
int x, y;
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
Color c = rimg.GetPixel(x, y);
int r = (c.R >> 4);
int g = (c.G >> 4);
int b = (c.B >> 4);
m_thumbnail[y * width + x] = (short)((r << 8) + (g << 4) + (b));
}
}
simg.Dispose();
rimg.Dispose();
return true;
}
.NETのSystem.Drawing.Bitmapクラスはラスターイメージを扱うためのもので、実体はGDI+をカプセル化したものである。
扱えるラスターイメージの画像フォーマットはWindows Bitmap、JPEG(Exifも含む)、GIF、PNG、TIFFで、一般的には十分であろう。GDI+で扱える画像フォーマットが増えればこのBitmapクラスで扱える画像フォーマットも増えると考えられる。Bitmapクラスでベクターイメージの画像を読めるかどうかは確認していない。
simg = new Bitmap(m_filePath);
ファイルパスを指定してBitmapクラスのインスタンスを得ているが、どのフォーマットの画像かは自動判定している。もちろん画像フォーマットの指定も可能。
Bitmap rimg = new Bitmap(simg, new Size(width, height));
読み込んだBitmapのオブジェクトと縦横のピクセル数を指定して新しくBitmapクラスのインスタンスを得ると、それは目的のサイズに縮小された画像となっている。
Color c = rimg.GetPixel(x, y);
縮小されたBitmapの各座標の色を得て、ビット数を落とし、サムネールとして組み立てる。
各色は8bitで、一般的にフルカラー(アルファチャンネルを加えてトゥルーカラー)と呼ばれるもの。実務的には問題ないが、ピクセルあたり16bitのグレースケールなどは表現できない。
simg.Dispose();
Bitmapクラスは明示的にDisposeをしないといつまでもファイルを開いたままになる。
Objective-C
- (BOOL)makeThumbnail:(int)pixcelNumber {
if (m_filePath == nil || [m_filePath length] == 0)
return NO;
NSImage *simg = [[NSImage alloc] initWithContentsOfFile: m_filePath];
if (simg == nil)
return NO;
int height = pixcelNumber;
int width = pixcelNumber;
[simg setScalesWhenResized: YES];
NSSize sz = {width, height};
[simg setSize:sz];
[simg lockFocus];
NSRect r = {0, 0, width, height};
NSBitmapImageRep *rimg = [[NSBitmapImageRep alloc] initWithFocusedViewRect:r];
[simg unlockFocus];
m_thumbnailCount = height * width;
m_thumbnail = malloc(m_thumbnailCount * sizeof(short));
int x, y;
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
NSColor *color = [rimg colorAtX:x y:y];
float red, green, blue, alpha;
[color getRed:&red green:&green blue:&blue alpha:&alpha];
int R = (int)(red * 255);
int G = (int)(green * 255);
int B = (int)(blue * 255);
int r = (R >> 4);
int g = (G >> 4);
int b = (B >> 4);
m_thumbnail[y * width + x] = (short)((r << 8) + (g << 4) + (b));
}
}
[simg release];
[rimg release];
return YES;
}
CocoaのNSImageクラスは多分内部的にQuickTimeやらCore Graphicsやらを使っていると考えられるが、実際どうなのかは不明。
扱える画像フォーマットはデフォルトではJPEG、GIF、TIFF、PICT、PDF、EPSとなっている。ExifはJPEGに含まれるようだ。PNGの記載がないが読めるかどうかは確認していない。
NSImage *simg = [[NSImage alloc] initWithContentsOfFile: m_filePath];
ファイルパスを指定してNSImageクラスのインスタンスを得ているが、どのフォーマットの画像かは自動判定している。ただしこのクラスでは画像フォーマットの指定はできない。
[simg lockFocus];
NSRect r = {0, 0, width, height};
NSBitmapImageRep *rimg = [[NSBitmapImageRep alloc] initWithFocusedViewRect:r];
[simg unlockFocus];
読み込んだ画像を目的サイズに縮小するには一旦ある種の描画を経てNSBitmapImageRepを得る必要がある。NSImageクラスにはその中のピクセルデータにアクセスするメソッドがないためだ。
NSImageクラスのオブジェクトは描画のフォーカスになることができ、フォーカスを設定後initWithFocusedViewRectによって縮小した画像を得る。
NSColor *color = [rimg colorAtX:x y:y];
float red, green, blue, alpha;
[color getRed:&red green:&green blue:&blue alpha:&alpha];
縮小されたNSBitmapImageRepの各座標の色を得て、ビット数を落とし、サムネールとして組み立てる。
NSColorクラスの各色は0〜1.0の実数で、階調の多いグレースケールなどにも対応できるようになっている。もちろん16bitグレースケールなどを出力するにはそれなりの専用デバイスが必要かと思う。
ともかくCocoaでは各色が8bitに制限されず、実数のまま扱えるAPIとなっている。
[simg release];
initで得たオブジェクトインスタンスはリリースしなくてはいけない。
Java
public boolean makeThumbnail(int pixcelNumber) {
if (m_filePath == null || m_filePath.length() == 0)
return false;
BufferedImage simg = null;
try {
simg = ImageIO.read(new File(m_filePath));
}
catch (Exception e) {
}
if (simg == null)
return false;
int height = pixcelNumber;
int width = pixcelNumber;
BufferedImage rimg = new BufferedImage(pixcelNumber, pixcelNumber,BufferedImage.TYPE_INT_ARGB);
Image iimg = simg.getScaledInstance(pixcelNumber, pixcelNumber, Image.SCALE_AREA_AVERAGING);
rimg.getGraphics().drawImage(iimg, 0, 0, null);
m_thumbnailCount = height * width;
m_thumbnail = new short[m_thumbnailCount];
int x, y;
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
Color c = new Color(rimg.getRGB(x, y));
int r = (c.getRed() >> 4);
int g = (c.getGreen() >> 4);
int b = (c.getBlue() >> 4);
m_thumbnail[y * width + x] = (short)((r << 8) + (g << 4) + (b));
}
}
return true;
}
Javaのjava.awt.image.BufferedImageはパッケージからわかるようにAWT(JavaのGUIシステムの基盤)の一部として提供されている。ただしここで画像ファイル読み込みに使っているjavax.imageio.ImageIOはJava Advanced Imagingの一部だったものをJava 1.4からデフォルトで使えるようにしたものだそうだ。
扱えるのはデフォルトではJPEG, GIF, PNGだそうだが、リファレンスマニュアルには記載がない。ExifはJPEGに含まれるようだ。オプションでJAIを入れると扱える画像フォーマットが増える。
simg = ImageIO.read(new File(m_filePath));
ファイルパスを指定してBufferedImageクラスのインスタンスを得ているが、どのフォーマットの画像かは自動判定している。読める画像フォーマットのMIMEタイプを得るメソッドもあるので、画像フォーマットを指定した読み込みも何らかの方法で可能なのだろう(未確認)。
Image iimg = simg.getScaledInstance(pixcelNumber, pixcelNumber, Image.SCALE_AREA_AVERAGING);
BufferedImage rimg = new BufferedImage(pixcelNumber, pixcelNumber,BufferedImage.TYPE_INT_ARGB);
rimg.getGraphics().drawImage(iimg, 0, 0, null);
画像サイズを変更するgetScaledInstanceメソッドがある。しかしそれで得られるのはImageであり、BufferedImageではない。ピクセルデータにアクセスするメソッドはjava.awt.image.BufferedImageクラスにはあるのだが抽象度の高いjava.awt.Imageクラスにはない。
やはりここでもある種の描画をBufferedImageクラスのインスタンスにおこなって縮小した画像を得る。
Color c = new Color(rimg.getRGB(x, y));
縮小されたBufferedImageの各座標の色を得て、ビット数を落とし、サムネールとして組み立てる。
色情報は32bit整数値で得られ、単純に8bitが4つ入っていると思われる。なぜjava.awt.Colorを返さずに整数なのかは不明だが、そこがクラス設計の哲学なのかもしれない。
2005.07.13
2005.09.18
「インデックス」へ戻る
OSTRACISM CO.
OSTRA / Takeshi Yoneki