セピアカラー化

昔懐かしい、セピア色の画像に変換します。

写真のプリントは、昔は銀塩が殆どでした。
現在では、普通のプリンターによりプリントアウト出来ますし、パソコンにデジタル画像として保存が出来るので、プリントする機会が少なくなりました。
写真を配る場合も、メールに添付したり、メモリースティックいれて、渡したりします。
プリント代よりも、メモリースティックの方が安くすみます。

銀塩写真の場合、経年変化により、セピア色に変色します。
経年変化の具合は、環境によって大きく左右され、保存状態によっては、殆ど変化しません。

セピア色に加工するプログラムは色々あるのですが、Delphiで作成してみました。

1. セピア化する方法として、グレースケール化した輝度値に、セピア色のパラメーターを乗じて変換
  セピア色 R=107 G=74 B=43   R=1とすると G=0.691 B=0.401 となりますが、 このままでは、赤が強くなるので、G,Bの値を少し大きくします。

輝度値をYとすると、
 赤 R=Y÷255 × 240           R=0.941
 緑 G=Y÷255 × 200           G=0.784
 青 B=Y÷255 × 145           B=0.568
の値が、インターネットで検索すると紹介されています。

此処では、少しでも画像を明るくするため
 赤 R=Y÷255 × 255    R=1
 緑 G=Y÷255 × 213    G=0.835
 青 B=Y÷255 × 121         B=0.475    写真のセピアらしくするためBは少し小さく
の値を設定してみました。
パラメーターによる方法では、全体的に画像が暗くなってしまいます。
最大輝度値が小さくなります。

2. GDIプラスのカラーマトリックスを使用する方法(カラーマトリックスについては、グレースケール化を参照して下さい。)

// Sepiaカラーマトリックス
ColorMatrix_SepiaScale : TColorMatrix = (
    ( 0.393, 0.349, 0.272, 0.0, 0.0),
    ( 0.769, 0.686, 0.534, 0.0, 0.0),
    ( 0.189, 0.168, 0.131, 0.0, 0.0),
    ( 0.0,    0.0,     0.0,   1.0, 0.0),
    ( 0.0,    0.0,     0.0,   0.0, 1.0));

グレースケール輝度変換係数と、セピア色の係数を合わせたような値となっています。
セピア色の濃度の調整が難しいです。

3. 輝度値からガンマ値により、RGBの値を作成する方法

  R=(Y/255)1/rR × 255
  G=(Y/255)1/rG × 255
  B=(Y/255)1/rB × 255

   
rB = 1.55    rG = 1.15   rB = 0.55

ガンマ補正カーブを利用して、輝度値をRGBに変換してセピア色に表示します。
各輝度において、セピア色となります。
視覚的に、グレー画像と輝度が変わりません。

ガンマ値

ガンマ値を使用すると、広い範囲の輝度のセピア化が出来ます。
青の値は、0.4位まで小さくしても良いかと思われます。

画像サンプル

画像サンプル

好みによって、差はあると思うのですが、ガンマ補正が、一番忠実に変換が出来ていると思われます。

サンプルプログラム

unit SepiaMain;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls, Vcl.ExtDlgs, System.Types, System.UITypes,
  GDIPAPI, GDIPOBJ, Math;

type
  TForm1 = class(TForm)
    FileOpen: TButton;
    Image1: TImage;
    Image2: TImage;
    OpenPictureDialog1: TOpenPictureDialog;
    SepiaColorBtn: TButton;
    SavePictureDialog1: TSavePictureDialog;
    FileSaveBtn: TButton;
    GDiMatGrayBtn: TButton;
    GammaBtn: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FileOpenClick(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure SepiaColorBtnClick(Sender: TObject);
    procedure FileSaveBtnClick(Sender: TObject);
    procedure GDiMatGrayBtnClick(Sender: TObject);
    procedure GammaBtnClick(Sender: TObject);
  private
    { Private 宣言 }
    procedure TemplateMake;      // Templateデーター作成
    procedure SepiaTemplate;     // SepiaTemplateデーター作成
    procedure GammaTemplate;     // SepiaGammaTemplateデーター作成
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

type
  TPrgbarry = array[0..0] of Trgbtriple;      // 24ビットカラーレコード 32ビット用はTRGBQuadArray
  Prgbarray  = ^TPrgbarry;                    // ポインター
                                              // 配列のポインターが必要なだけなので、長さは1 [0..0]で問題ありません。
implementation

{$R *.dfm}

const
  OpenFileFilter =
    '画像ファイル|*.png;*.jpg;*.gif;*.bmp;*.tif;*.ico;*.wdp'+
    '|*.png|*.png' +
    '|*.jpg|*.jpg' +
    '|*.gif|*.gif' +
    '|*.bmp|*.bmp' +
    '|*.tif|*.tif' +
    '|*.ico|*.ico' +
    '|*.wdp|*.wdp';

  SaveFileFilter =
    '画像ファイル|*.png;*.jpg;*.gif;*.bmp;*.tif;*.wdp' +
    '|*.png|*.png' +
    '|*.jpg|*.jpg' +
    '|*.gif|*.gif' +
    '|*.bmp|*.bmp' +
    '|*.tif|*.tif' +
    '|*.wdp|*.wdp';

  ImageHW   = 384;                            // 表示枠サイズ

  RK    = 0.298912;                           // グレースケール輝度係数 赤
  GK    = 0.586611;                           // グレースケール輝度係数 緑
  BK    = 0.114478;                           // グレースケール輝度係数 青
  BISU  = 1024;                               // 整数化倍数
  RSISF = 10;                                 // ビットシフト数

  SepiaR = 1.000;                             // セピア係数赤
  SepiaG = 0.835;                             // セピア係数緑
  SepiaB = 0.475;                             // セピア係数青
{
  SepiaR = 0.941;                             // セピア係数赤
  SepiaG = 0.784;                             // セピア係数緑
  SepiaB = 0.447;                             // セピア係数青
}

  RGamma = 1.55;                              // 赤ガンマ値
  GGamma = 1.15;                              // 緑ガンマ値
  BGamma = 0.55;                              // 青ガンマ値


// Sepiaマトリックス
  ColorMatrix_SepiaScale : TColorMatrix = (
    ( 0.393,  0.349,  0.272,  0.0,  0.0),
    ( 0.769,  0.686,  0.534,  0.0,  0.0),
    ( 0.189,  0.168,  0.131,  0.0,  0.0),
    ( 0.0,    0.0,    0.0,    1.0,  0.0),
    ( 0.0,    0.0,    0.0,    0.0,  1.0));

var
  InBitmap        : TBitmap;                  // ビットマップ
  OutBitmap24     : TBitmap;
  GHeight, GWidth : integer;                  // ソース画像サイズ
  Vrect           : Trect;                    // 表示枠
  IHeight, IWidth : Integer;                  // 表示サイズ
  RTemp           : array[0..255] of Cardinal;  // テンプレート用配列 赤
  GTemp           : array[0..255] of Cardinal;  // テンプレート用配列  緑
  BTemp           : array[0..255] of Cardinal;  // テンプレート用配列  青

  RSTemp          : array[0..255] of Byte;      // セピアテンプレート用配列 赤
  GSTemp          : array[0..255] of Byte;      // セピアテンプレート用配列  緑
  BSTemp          : array[0..255] of Byte;      // セピアテンプレート用配列  青

  RGTemp          : array[0..255] of Byte;      // セピアガンマテンプレート用配列 赤
  GGTemp          : array[0..255] of Byte;      // セピアガンマテンプレート用配列  緑
  BGTemp          : array[0..255] of Byte;      // セピアガンマテンプレート用配列  青

procedure TForm1.GammaTemplate;     // SepiaGammaTemplateデーター作成
var
  II : Integer;
begin
  for II := 0 to 255 do begin
    RGTemp[II] := Round(255 * Power(II / 255, 1 / RGamma)); // セピアテンプレート赤
    GGTemp[II] := Round(255 * Power(II / 255, 1 / GGamma)); // セピアテンプレート緑
    BGTemp[II] := Round(255 * Power(II / 255, 1 / BGamma)); // セピアテンプレート青
  end;
end;

procedure TForm1.SepiaTemplate;     // SepiaTemplateデーター作成
var
  II : Integer;
begin
  for II := 0 to 255 do begin
    RSTemp[II] := Round(II * SepiaR);           // セピアテンプレート赤
    GSTemp[II] := Round(II * SepiaG);           // セピアテンプレート緑
    BSTemp[II] := Round(II * SepiaB);           // セピアテンプレート青
  end;
end;

// NTSC 係数を1024倍してテンプレートデーター作成
procedure TForm1.TemplateMake;      // Templateデーター作成
var
  II : Integer;
begin
  for II := 0 to 255 do begin
    RTemp[II] := round(RK * BISU * II);                       // 赤テンプレート
    GTemp[II] := round(GK * BISU * II);                       // 緑テンプレート
    BTemp[II] := round(BK * BISU * II);                       // 青テンプレート
  end;
end;

// ファイルのオープン WIC がファイルの種類が多いので使用
procedure TForm1.FileOpenClick(Sender: TObject);
var
  WIC         : TWICImage;
  InFilename  : String;
begin
  SepiaColorBtn.Enabled  := False;
  GDiMatGrayBtn.Enabled  := False;
  GammaBtn.Enabled      := False;
  FileSaveBtn.Enabled   := False;
  VRect := Rect(0, 0, Image1.Width, Image1.Height);
  Image1.Canvas.Brush.Style := bsSolid;
  Image1.Canvas.Brush.Color := clBtnface;
  Image1.Canvas.FillRect(VRect);                              // Canvas 画像消去
  Image2.Canvas.Brush.Style := bsSolid;
  Image2.Canvas.Brush.Color := clBtnface;
  Image2.Canvas.FillRect(VRect);                              // Canvas 画像消去
  OpenPictureDialog1.Filter := OpenFileFilter;                // ファイルオープンフィルターの設定
  if OpenPictureDialog1.Execute then                          // ファイルが指定されたら
    begin
      WIC := TWICImage.Create;                                // TWICImageの生成
      try
        InFilename := OpenPictureDialog1.FileName;            // ファイル名の取得
        WIC.LoadFromFile(InFilename);                         // 画像の読み込み
        GHeight := WIC.Height;                                // 画像高さ取得
        GWidth  := WIC.Width;                                 // 画像幅
        IWidth  := ImageHW;                                   // 出力先イメージ1の幅
        IHeight := ImageHW;                                   // 出力先イメージ1の高さ
        if GHeight <= GWidth then                             // 縦横比により出力サイズ設定
          IHeight := Round(IWidth * GHeight / GWidth)
        else
          IWidth := Round(IHeight * GWidth / GHeight);
        Image1.Width := IWidth;
        Image1.Height:= IHeight;
        Image1.Picture.Bitmap.SetSize(IWidth, IHeight);       // 表示用ビットマップサイズの設定
        Image2.Width := IWidth;
        Image2.Height:= IHeight;
        Image2.Picture.Bitmap.SetSize(IWidth, IHeight);       // 表示用ビットマップサイズの設定
        VRect := Rect(0, 0, IWidth, IHeight);                 // 出力枠設定
        Image1.Canvas.StretchDraw(VRect, WIC);                // 出力枠に変倍出力
        InBitmap.Width    := GWidth;
        InBitmap.Height   := GHeight;
        InBitmap.Canvas.Draw(0, 0, WIC);                      // DrawでInBitmapに入力画像設定フォーマット24ビットに変換されます
        OutBitmap24.Width := GWidth;
        OutBitmap24.Height:= GHeight;
      finally
        WIC.Free;                                             // TWICImage 解放
      end;
    end
    else Exit;
  SepiaColorBtn.Enabled   := True;
  GDiMatGrayBtn.Enabled   := True;
  GammaBtn.Enabled        := True;
end;

// NTSC係数で輝度変換し更にセピア色に変換します
procedure TForm1.SepiaColorBtnClick(Sender: TObject);        // セピア色変換
var
  II, JJ        : Integer;
  InpPrgbarray  : Prgbarray;
  OutParray     : Prgbarray;
  OutInt        : DWORD;
  OutByte       : BYTE;
begin
  for II := 0 to GHeight - 1 do begin
    InpPrgbarray  := InBitmap.ScanLine[II];                   // ラインポインタ設定
    OutParray     := OutBitmap24.ScanLine[II];
    for JJ := 0 to GWidth - 1 do begin                        // 三色加算
      OutInt :=   RTemp[InpPrgbarray[JJ].rgbtRed]             // 赤テンプレート
                + GTemp[InpPrgbarray[JJ].rgbtGreen]           // 緑テンプレート
                + BTemp[InpPrgbarray[JJ].rgbtBlue];           // 青テンプレート
      OutByte := OutInt shr RSISF;                            // 10ビット右シフト OutInt div 1024
      OutParray[JJ].rgbtBlue  := BSTemp[OutByte];             // セピアテンプレート青適用
      OutParray[JJ].rgbtGreen := GSTemp[OutByte];             // セピアテンプレート緑適用
      OutParray[JJ].rgbtRed   := RSTemp[OutByte];             // セピアテンプレート赤適用
    end;
  end;
  Image2.Canvas.StretchDraw(VRect, OutBitmap24);              // 出力枠に変倍出力
  FileSaveBtn.Enabled   := True;
end;

// マトリックスによるセピア変換
procedure TForm1.GDiMatGrayBtnClick(Sender: TObject);
var
  Gpgraph : TGPGraphics;
  GPImage  : TGPImage;
  GPImageAttr : TGPImageAttributes;
  Astream     : TMemoryStream;
begin
  // GPImage := TGPImage.Create(InFilename);   // TGPImageにファイル名関連付け
  // ファイルから読み込む代わりにメモリーへ出力しメモリーから読み込みます
  // メモリーストリームを作成し inbitmap を出力
  Astream := TMemoryStream.Create;
  InBitmap.SaveToStream(Astream);

  // メモリーストリームから,TGPBitmapのオブジェクトに読み込み指定
  GPImage := TGPBitmap.Create(TStreamAdapter.Create(AStream, soReference));
//  GPImage := TGPImage.Create(InFilename);                 // TGPImageにファイル名関連付け

  // GD+にTBitmapの描画ハンドル設定
  Gpgraph := TGPGraphics.Create(OutBitmap24.Canvas.Handle);

  // TGPImageAttributesのオブジェクトを生成
  GPImageAttr := TGPImageAttributes.Create;
  GPImageAttr.SetGamma(1.0);                              // ガンマ値設定 1.0 なので無くても可

  // 使用するマトリックスをアトリビュートに設定
  GPImageAttr.SetColorMatrix(ColorMatrix_SepiaScale);

  // TCanvasのStretchDrawに相当する
  // GpgraphのHandleがOutBitmap24.Canvasに指定されているのでOutBitmap24.Canvasに描画されます
  Gpgraph.DrawImage(                                      // Gpgraph (OutBitmap24) に描画
              GPImage,                                    // 元画像
              MakeRect(0, 0, GWidth, GHeight),            // 出力位置 サイズ 
              0, 0, GPImage.GetWidth, GPImage.GetHeight,  // 入力位置 サイズ
              UnitPixel,                                  // ピクセル
              GPImageAttr);                               // アトリビュート

  Image2.Canvas.StretchDraw(Vrect, OutBitmap24);          // Image1に Dbitmap変倍表示

  //Createしたオブジェクトは解放処理します。
  GPImageAttr.Free;
  GPImage.Free;
  Gpgraph.Free;
  Astream.Free;
  FileSaveBtn.Enabled   := True;
end;

// ガンマ係数によるセピア変換
procedure TForm1.GammaBtnClick(Sender: TObject);
var
  II, JJ        : Integer;
  InpPrgbarray  : Prgbarray;
  OutParray     : Prgbarray;
  OutInt        : DWORD;
  OutByte       : BYTE;
begin
  for II := 0 to GHeight - 1 do begin
    InpPrgbarray  := InBitmap.ScanLine[II];                   // ラインポインタ設定
    OutParray     := OutBitmap24.ScanLine[II];
    for JJ := 0 to GWidth - 1 do begin                        // 三色加算
      OutInt :=   RTemp[InpPrgbarray[JJ].rgbtRed]             // 赤テンプレート
                + GTemp[InpPrgbarray[JJ].rgbtGreen]           // 緑テンプレート
                + BTemp[InpPrgbarray[JJ].rgbtBlue];           // 青テンプレート
      OutByte := OutInt shr RSISF;                            // 10ビット右シフト OutInt div 1024
      OutParray[JJ].rgbtBlue  := BGTemp[OutByte];             // セピアガンマテンプレート青適用
      OutParray[JJ].rgbtGreen := GGTemp[OutByte];             // セピアガンマテンプレート緑適用
      OutParray[JJ].rgbtRed   := RGTemp[OutByte];             // セピアガンマテンプレート赤適用
    end;
  end;
  Image2.Canvas.StretchDraw(VRect, OutBitmap24);              // 出力枠に変倍出力
  FileSaveBtn.Enabled   := True;
end;

// ファイルの保存
procedure TForm1.FileSaveBtnClick(Sender: TObject);
var
  WIC     : TWicImage;
  WICF    : TWicImageFormat;

  Fname   : String;
  ExeStr  : String;
  FnameTop: String;
  Findex  : integer;

  function WFormatSet: Boolean;                                            // 拡張子によるファイルフォーマット設定
  begin
    Result := false;
    ExeStr := LowerCase(ExeStr);
    if ExeStr = '.jpg'  then  begin WICF := Wifjpeg; Result := True; end;
    if ExeStr = '.jpeg' then  begin WICF := Wifjpeg; Result := True; end;
    if ExeStr = '.tif'  then  begin WICF := Wiftiff; Result := True; end;
    if ExeStr = '.tiff' then  begin WICF := Wiftiff; Result := True; end;
    if ExeStr = '.png'  then  begin WICF := Wifpng;  Result := True; end;
    if ExeStr = '.gif'  then  begin WICF := Wifgif;  Result := True; end;
    if ExeStr = '.bmp'  then  begin WICF := Wifbmp;  Result := True; end;
    if ExeStr = '.wdp'  then  begin WICF := WifWMPhoto; Result := True; end;
    if ExeStr = '.hdp'  then  begin WICF := WifWMPhoto; Result := True; end;
  end;

begin
  SavePictureDialog1.Filter := SaveFileFilter;
//  SavePictureDialog1.DefaultExt := GraphicExtension(TWicImage);
  if not SavePictureDialog1.Execute then exit;
  ExeStr := ExtractFileExt(SavePictureDialog1.FileName);
  if ExeStr = '' then begin                                                // 拡張子がなかったら
    Findex := SavePictureDialog1.FilterIndex;                              // FilterIndexによる拡張子の設定
    case Findex of
      1, 3 : Fname := ChangeFileExt(SavePictureDialog1.FileName,'.jpg');   // 拡張子の設定
         2 : Fname := ChangeFileExt(SavePictureDialog1.FileName,'.png');   // 拡張子の設定
         4 : Fname := ChangeFileExt(SavePictureDialog1.FileName,'.gif');   // 拡張子の設定
         5 : Fname := ChangeFileExt(SavePictureDialog1.FileName,'.bmp');   // 拡張子の設定
         6 : Fname := ChangeFileExt(SavePictureDialog1.FileName,'.tif');   // 拡張子の設定
         7 : Fname := ChangeFileExt(SavePictureDialog1.FileName,'.wdp');   // 拡張子の設定
    end;
  end
  else
    Fname := SavePictureDialog1.FileName;
  ExeStr := ExtractFileExt(Fname);                                         // 拡張子だけ取り出し
  if not WFormatSet then begin                                             // 拡張子によるファイルフォーマット設定と確認
    application.MessageBox('ファイルの拡張子が間違っています。','注意', 0);
    exit;
  end;
  FnameTop := ExtractFileName(Fname);                                      // ファイル名だけ取り出し
  if Length(FnameTop) = Length(ExeStr) then begin                          // ファイル名の長さ確認
    application.MessageBox('ファイル名がありません。','注意', 0);
    exit;
  end;

  if FileExists(Fname) then                                                // ファイル名によるファイル検索
    if MessageDlg('既に同じ名前のファイルがあります上書きしますか ' + ExtractFileName(Fname) + '?',
                                                      mtConfirmation, [mbYes, mbNo], 0, mbNo) = IDNo then exit;

  WIC := TWicImage.Create;                                                 // TWicImage生成
  try
    WIC.Assign(OutBitmap24);                                               // TWicImageにビットマップデーター割り付け
    WIC.ImageFormat := WICF;                                               // 保存フォーマットセット
    WIC.SaveTofile(Fname);                                                 // ファイルの書き出し
  finally
    WIC.Free;                                                              // TWicImage解放
  end;
end;

// 初期設定
procedure TForm1.FormCreate(Sender: TObject);
begin
  SepiaColorBtn.Enabled := False;
  FileSaveBtn.Enabled   := False;
  GDiMatGrayBtn.Enabled := False;
  GammaBtn.Enabled      := False;
  Image1.Width  := ImageHW;
  Image1.Height := ImageHW;
  Image2.Width  := ImageHW;
  Image2.Height := ImageHW;
  InBitmap      := TBitmap.Create;        // 入力画像用
  OutBitmap24   := TBitmap.Create;        // 24ビット出力用
  InBitmap.PixelFormat    := pf24bit;     // 24ビットカラーに設定
  OutBitmap24.PixelFormat := pf24bit;     // 24ビットカラーに設定
  TemplateMake;                           // Templateデーター作成
  SepiaTemplate;                          // SepiaTemplateデーター作成
  GammaTemplate;                          // SepiaGammaTemplateデーター作成
end;

// ビットマップの解法
procedure TForm1.FormDestroy(Sender: TObject);
begin
  InBitmap.Free;
  OutBitmap24.Free;
end;


end.

         download SepiaColor.zip

        画像処理一覧へ戻る

        最初に戻る