- PR -

C#での8bitグレースケールJpegの作成方法

投稿者投稿内容
ちゃんこ
会議室デビュー日: 2007/05/15
投稿数: 6
投稿日時: 2007-05-15 18:33
デジカメの写真や、コンピュータ画面のハードコピーから作成したJPEG・BMPファイルを入力し、8bitグレースケール処理を施して出力したいと考えています。

8bitグレースケールBMPとしての出力は以下のようなコードで実現できました。
(※すみません。すべて書くと結構長いので省略しています・・・わかりにくければご指摘いただければと思います)
--------------------------------------------------------------
Bitmap inputImage = new Bitmap(@"C:\\変換したい画像のパス");
Bitmap output = null;
int biWidth = inputImage.Width;
int biHeight = inputImage.Height;

//出力BITMAP画像のヘッダー
byte[] fileHeaderByteCol = BITMAPFILEHEADER構造体のbyte[]への変換();
byte[] infoHeaderByteCol = BITMAPINFOHEADER構造体のbyte[]への変換();

//カラーパレットの取得
byte[] colorParette = 8bitグレースケールカラーパレット();
//画像データの取得
byte[] imageData = 画像データの取得();

MemoryStream memoryStream = new MemoryStream();
memoryStream.Write(fileHeaderByteCol, 0, fileHeaderByteCol.Length);
memoryStream.Write(infoHeaderByteCol, 0, infoHeaderByteCol.Length);
memoryStream.Write(colorParette, 0, colorParette.Length);
memoryStream.Write(imageData, 0, imageData.Length);

//8bitグレースケール画像を取得
output = new Bitmap(memoryStream);
output.Save(@"C:\\8bitグレースケール.bmp", ImageFormat.Bmp);
--------------------------------------------------------------

Jpegについて、BMPのときの最後のoutput.Saveメソッドの第二引数で作成する
画像フォーマットの種類が指定できるため、ここにJpgを指定してみたのですが、
見た目はグレースケールなのですが24bitのものになってしまいます。
これについて何か方法をご存知の方おられましたら、教えていただけないでしょうか?

[ メッセージ編集済み 編集者: ちゃんこ 編集日時 2007-05-15 18:37 ]
ダッチ
大ベテラン
会議室デビュー日: 2005/10/31
投稿数: 113
投稿日時: 2007-05-15 22:11
何にも試していなくて申し訳ないのですが、Bitmap を生成するコンストラクタの引数にSystem.Drawing.Imaging.PixelFormat 列挙体を受け取れるバージョンがあります。そこに Format8bppIndexed を指定してみてはいかがでしょうか。
ちゃんこ
会議室デビュー日: 2007/05/15
投稿数: 6
投稿日時: 2007-05-16 09:57
ダッチさんありがとうございます。
デバッグモードで現時点のソースを追ってみたのですが、Saveする前のBitmapオブジェクトのPixelFormatの値は、すでにPixelFormat.Format8bppIndexedでした。

上記の点はちょっと気になるのですが、ダッチさんに頂いたように、Bitmapを生成するコンストラクタの引数にPixelFormatをとれるものが2つありますので、いずれかで8bitを指定してインスタンス化したあとに、保持しているbyte[]画像データを流してみようと思います。うまく処理できましたらまた投稿いたします。
ダッチ
大ベテラン
会議室デビュー日: 2005/10/31
投稿数: 113
投稿日時: 2007-05-16 12:53
PixelFormat.Format8bppIndexed でやってもだめですね。

変わりに、Encoder.ColorDepth フィールド を使用して 8bit に指定したのですが、それでも 24bit で出力されてしまいますね。
お役に立てなくて申し訳ありません。
ちゃんこ
会議室デビュー日: 2007/05/15
投稿数: 6
投稿日時: 2007-05-16 13:03
いえいえ、そんなことありません。
私は画像処理に関する実装を今までしたことがありませんでしたので、他の方がどこに着目されているかという点についても参考になります。
Encoder.ColorDepthというものがあるのですね。どういったものなのかを合わせてみてみることにします。
seai
ベテラン
会議室デビュー日: 2007/04/10
投稿数: 60
投稿日時: 2007-05-16 15:55
こんにちわ。

色の扱いの観点から1点。
8ビットグレースケールということですが、
アプリの開発環境は当然カラーですよね?
だとすれば指定されているビット数はRGB単色に対してと思われます。
R:G:B=n:n:n(n=0〜255)で白(グレー)を表現します。
従って8ビットグレースケールをカラー環境で表示する場合は、

for n = 0 to 255
color = RGB(n,n,n)
line (x1 + n * xhaba , y1)-(x1 + (n + 1) * xhaba , y2), color, BF
next

となります。
(あまりいい例でなくてすいません。昔VB6で作ったコードです。)
従ってRGBそれぞれ8ビットですから
8x3=24
となりますから24ビットで正解のはずですがいかがでしょうか。

ちゃんこ
会議室デビュー日: 2007/05/15
投稿数: 6
投稿日時: 2007-05-16 17:07
引用:

seaiさんの書き込み (2007-05-16 15:55) より:
こんにちわ。

色の扱いの観点から1点。
8ビットグレースケールということですが、
アプリの開発環境は当然カラーですよね?
だとすれば指定されているビット数はRGB単色に対してと思われます。
R:G:B=n:n:n(n=0〜255)で白(グレー)を表現します。
従って8ビットグレースケールをカラー環境で表示する場合は、

for n = 0 to 255
color = RGB(n,n,n)
line (x1 + n * xhaba , y1)-(x1 + (n + 1) * xhaba , y2), color, BF
next

となります。
(あまりいい例でなくてすいません。昔VB6で作ったコードです。)
従ってRGBそれぞれ8ビットですから
8x3=24
となりますから24ビットで正解のはずですがいかがでしょうか。





すいません。ちょっと私の書き方がまずかったのかもしれませんので、補足します。
私のほうで目標としているのは、8bitグレースケールのJpgファイルを出力することです。
市販のソフトでいうとPaintShopやPhotoShopの機能には、8bitグレースケールのJpgファイルを出力する機能がありまして、これを利用して作成されたJpgファイルは、Bitmapクラスで読み込んだときに、Bitmap.PixelFormatの値はFormat8bppIndexedになります。よって8bitグレースケールのJpgファイルというものが存在しうると想定して進めており、同等の機能を実現するようなコードを模索している状態です。
(そもそも24bitで正解ということであれば、ここが間違いですが・・・)
ちゃんこ
会議室デビュー日: 2007/05/15
投稿数: 6
投稿日時: 2007-05-16 17:37
概略的なコードだけですと、再現ができませんので、ちょっと長くなってしまいますが記載させていただければと思います。
※元画像からのデータ取得はパフォーマンスアップのためにBitmap.GetPixcelメソッドを使わずunsafeコードで記述しています。

--------------------------------------------------------
using System.Runtime.InteropServices;
using System.Drawing.Imaging;
using System.IO;

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct BITMAPFILEHEADER
{
public ushort bfType;
public uint bfSize;
public ushort bfReserved1;
public ushort bfReserved2;
public uint bfOffBits;
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct BITMAPINFOHEADER
{
public uint biSize;
public int biWidth;
public int biHeight;
public ushort biPlanes;
public ushort biBitCount;
public uint biCompression;
public uint biSizeImage;
public int biXPelsPerMeter;
public int biYPelsPerMeter;
public uint biClrUsed;
public uint biClrImportant;
public const int BI_RGB = 0;
}

private BITMAPINFOHEADER GetBITMAPINFOHEADER(int imageWidth, int imageHeight)
{
BITMAPINFOHEADER infoHeader = new BITMAPINFOHEADER();
infoHeader.biSize = 40;
infoHeader.biWidth = imageWidth;
infoHeader.biHeight = imageHeight;
infoHeader.biPlanes = 1;
infoHeader.biBitCount = 8;
infoHeader.biCompression = BITMAPINFOHEADER.BI_RGB;
infoHeader.biSizeImage = 0;
infoHeader.biXPelsPerMeter = 0;
infoHeader.biYPelsPerMeter = 0;
infoHeader.biClrUsed = 0;
infoHeader.biClrImportant = 0;
return infoHeader;
}

private BITMAPFILEHEADER GetBITMAPFILEHEADER(int imageWidth, int imageHeight)
{
BITMAPFILEHEADER fileHeader = new BITMAPFILEHEADER();
fileHeader.bfType = (ushort)0x4D42;
fileHeader.bfOffBits = 54 + 1024;
fileHeader.bfSize = fileHeader.bfOffBits + (uint)(imageWidth * imageHeight);
fileHeader.bfReserved1 = 0;
fileHeader.bfReserved2 = 0;
return fileHeader;
}

private static byte[] ConvertStructureToByteArray(Object target)
{
byte[] bytes = null;
IntPtr ptr = IntPtr.Zero;
try
{
ptr = Marshal.AllocHGlobal(Marshal.SizeOf(target));
Marshal.StructureToPtr(target, ptr, false);
bytes = new byte[Marshal.SizeOf(target)];
Marshal.Copy(ptr, bytes, 0, Marshal.SizeOf(target));
return bytes;
}
finally
{
Marshal.FreeHGlobal(ptr);
}
}

private byte[] GetMonoChroColorTable()
{
byte[] colorTable = new byte[1024];
for (int cnt = 0; cnt < 256; cnt++)
{
colorTable[cnt * 4] = (byte)cnt;
colorTable[cnt * 4 + 1] = (byte)cnt;
colorTable[cnt * 4 + 2] = (byte)cnt;
colorTable[cnt * 4 + 3] = 0;
}
return colorTable;
}

private byte[] GetImageData2(Bitmap inputImage)
{
List<Byte> imageDataCol = new List<byte>();
BitmapData bitMapData = inputImage.LockBits(new Rectangle(0, 0, inputImage.Width, inputImage.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
int x, y, ySrc;
unsafe
{
uint* src = (uint*)(void*)bitMapData.Scan0;
for (y = inputImage.Height - 1; y >= 0; y--)
{
ySrc = y * bitMapData.Stride / sizeof(uint);
for (x = 0; x < inputImage.Width; x++)
{
imageDataCol.Add(Color.FromArgb((int)src[ySrc + x]).R);
}
if (inputImage.Width % 4 == 0)
{
continue;
}
for (int cnt = 0; cnt < 4 - inputImage.Width % 4; cnt++)
{
imageDataCol.Add(byte.MinValue);
}
}
}
inputImage.UnlockBits(bitMapData);
return imageDataCol.ToArray();
}

private Bitmap GetMonoChroBMPFile(Bitmap inputImage)
{
Bitmap output = null;
int biWidth = inputImage.Width;
int biHeight = inputImage.Height;
BITMAPFILEHEADER bitmapFileHeader = this.GetBITMAPFILEHEADER(biWidth, biHeight);
byte[] fileHeaderByteCol = ConvertStructureToByteArray(bitmapFileHeader);
BITMAPINFOHEADER bitmapInfoHeader = this.GetBITMAPINFOHEADER(biWidth, biHeight);
byte[] infoHeaderByteCol = ConvertStructureToByteArray(bitmapInfoHeader);
byte[] colorParette = this.GetMonoChroColorTable();
byte[] imageData = this.GetImageData2(inputImage);
MemoryStream memoryStream = new MemoryStream();
memoryStream.Write(fileHeaderByteCol, 0, fileHeaderByteCol.Length);
memoryStream.Write(infoHeaderByteCol, 0, infoHeaderByteCol.Length);
memoryStream.Write(colorParette, 0, colorParette.Length);
memoryStream.Write(imageData, 0, imageData.Length);
output = new Bitmap(memoryStream);
return output;
}

//上記のソースで、8bitグレースケールのBMPファイルは出力できます。
//(Bitmapオブジェクト.Save("出力先パス", ImageFormat.Bmp))
//但し、以下のようにJpegファイルとして出力すると24bitになってしまいます。

public void Save(Bitmap bmp, int quality, string outputFilePath)
{
EncoderParameters eps = new EncoderParameters(1);
EncoderParameter ep1 = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
eps.Param[0] = ep1;
ImageCodecInfo imageCodecInfo = GetEncoderInfo("image/jpeg");
string extension = imageCodecInfo.FilenameExtension.Split(';')[0];
extension = Path.GetExtension(extension).ToLower();
string saveName = Path.ChangeExtension(outputFilePath, extension);
bmp.Save(saveName, imageCodecInfo, eps);
}

public static ImageCodecInfo GetEncoderInfo(string mineType)
{
ImageCodecInfo[] encs = ImageCodecInfo.GetImageEncoders();
foreach (ImageCodecInfo enc in encs)
{
if (enc.MimeType == mineType)
{
return enc;
}
}
return null;
}
--------------------------------------------------------

スキルアップ/キャリアアップ(JOB@IT)