RGBをHSVに変換して指定した色相範囲を変換する(VCL) ~Delphiでお手軽プログラミング

RGBをHSVに変換して指定した色相範囲を変換する(VCL) ~Delphiでお手軽プログラミング

(参考)バイキュービック法(bicubic)で拡大縮小する(VCL)
(参考)バイキュービック法(bicubic)で拡大縮小する(FMX)
(参考)バイラテラルフィルタ(bilateral filter)で美肌に加工(VCL)
(参考)バイラテラルフィルタ(bilateral filter)で美肌に加工(FMX)
(参考)ガンマ(gamma)補正を画像に適用する(VCL)
(参考)ガンマ(gamma)補正を画像に適用する(FMX)
(参考)ソーベルフィルタ(Sobel filter)で境界(エッジ)検出(VCL)

プロジェクトの作成とソースコードの記述

プロジェクトを新規作成(VCLアプリケーション)し、以下のようにコンポーネントを配置する。
Image4のPictureプロパティから、色相を変換したい画像をロードしておく。
TButton1をダブルクリックして、以下ソースコードを記述する。
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
  Vcl.Imaging.jpeg,Vcl.Imaging.pngimage;

type
  TForm1 = class(TForm)
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    ScrollBar1: TScrollBar;
    ScrollBar2: TScrollBar;
    ScrollBar3: TScrollBar;
    Image1: TImage;
    Image2: TImage;
    Image3: TImage;
    Button1: TButton;
    ScrollBar4: TScrollBar;
    Label4: TLabel;
    ScrollBox1: TScrollBox;
    Image4: TImage;
    ScrollBox2: TScrollBox;
    Image5: TImage;
    procedure FormCreate(Sender: TObject);
    procedure Image4MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure ScrollBar1Change(Sender: TObject);
    procedure ScrollBar2Change(Sender: TObject);
    procedure ScrollBar4Change(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    procedure DrawHue();
  end;

  TRGB=record
    B,G,R:Byte;
  end;
  TRGBArr=array[0..32767] of TRGB;
  PRGBArr=^TRGBArr;

  //H(0~360),S(0~1),V(0~1)
  THSV=record
    H,S,V:Single;
  end;

//HSVとは
//  色相(Hue 0~360°)
//  彩度(Saturation・Chroma 0~100%)
//  明度(Value・Brightness 0~100%)

//RGB から HSV(HSB) に変換
function RGBtoHSV(frgb:TRGB):THSV;
//HSV(HSB) から RGB に変換
function HSVtoRGB(fhsv:THSV):TRGB;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses System.Math;

//RGB から HSV(HSB) に変換
function RGBtoHSV(frgb:TRGB):THSV;
var RGBMax,RGBMin:Byte;
begin
  RGBMax:=System.Math.Max(frgb.R,frgb.G);
  RGBMax:=System.Math.Max(RGBMax,frgb.B);
  RGBMin:=System.Math.Min(frgb.R,frgb.G);
  RGBMin:=System.Math.Min(RGBMin,frgb.B);
  //色相(Hue 0~360°)
  if RGBMax=RGBMin then
    result.H:=0
  else if frgb.R=RGBMax then
    result.H:=60*(frgb.G-frgb.B)/(RGBMax-RGBMin)
  else if frgb.G=RGBMax then
    result.H:=60*(frgb.B-frgb.R)/(RGBMax-RGBMin)+120
  else
    result.H:=60*(frgb.R-frgb.G)/(RGBMax-RGBMin)+240;
  result.H:=FMod(result.H, 360);
  if result.H<0 then result.H:=result.H+360;
  //彩度(Saturation or Chroma 0~100%)
  if RGBMax=0 then
    result.S:=0
  else
    result.S:=(RGBMax-RGBMin) / RGBMax;
  //明度(Value or Brightness 0~100%)
  result.V:=RGBMax/255;
end;

//HSV(HSB) から RGB に変換
function HSVtoRGB(fhsv:THSV):TRGB;
var RGBMax,RGBMin:Byte;
begin
  fhsv.H:=FMod(fhsv.H,360);
  if fhsv.H<0 then fhsv.H:=fhsv.H+360;
  RGBMax:=Round(fhsv.V*255);
  RGBMin:=Round(RGBMax-(fhsv.S*RGBMax));
  if fhsv.S=0 then
  begin
    result.R:=RGBMax;
    result.G:=RGBMax;
    result.B:=RGBMax;
  end
  else if fhsv.H<60 then
  begin
    result.R:=RGBMax;
    result.G:=Round((fhsv.H-  0)/60*(RGBMax-RGBMin)+RGBMin);
    result.B:=RGBMin;
  end
  else if fhsv.H<120 then
  begin
    result.R:=Round((120-fhsv.H)/60*(RGBMax-RGBMin)+RGBMin);
    result.G:=RGBMax;
    result.B:=RGBMin;
  end
  else if fhsv.H<180 then
  begin
    result.R:=RGBMin;
    result.G:=RGBMax;
    result.B:=Round((fhsv.H-120)/60*(RGBMax-RGBMin)+RGBMin);
  end
  else if fhsv.H<240 then
  begin
    result.R:=RGBMin;
    result.G:=Round((240-fhsv.H)/60*(RGBMax-RGBMin)+RGBMin);
    result.B:=RGBMax;
  end
  else if fhsv.H<300 then
  begin
    result.R:=Round((fhsv.H-240)/60*(RGBMax-RGBMin)+RGBMin);
    result.G:=RGBMin;
    result.B:=RGBMax;
  end
  else
  begin
    result.R:=RGBMax;
    result.G:=RGBMin;
    result.B:=Round((360-fhsv.H)/60*(RGBMax-RGBMin)+RGBMin);
  end;
end;


{ TForm1 }

{ Image4.Picture.Bitmap 画像で
  色相が ScrollBar1.Position ± ScrollBar2.Position のピクセルの
  色相を ScrollBar3.Position ± ScrollBar2.Position に変換
  [つまり色相を+(ScrollBar3.Position-ScrollBar1.Position)] し、
  Image5.Picture.Bitmap に描画する
}
procedure TForm1.Button1Click(Sender: TObject);
var sbmp,dbmp:TBitmap;
    fsrgb,fdrgb:PRGBArr;
    frgb:TRGB;
    fhsv:THSV;
    x,y:Integer;
    h1,h2:Integer;
    h11,h12,h21,h22:Integer;
begin
  sbmp:=TBitmap.Create;
  dbmp:=TBitmap.Create;
  try
    sbmp.Assign(Image4.Picture.Graphic);
    sbmp.PixelFormat:=pf24bit;
    dbmp.Width:=sbmp.Width;
    dbmp.Height:=sbmp.Height;
    dbmp.PixelFormat:=pf24bit;
    for y := 0 to sbmp.Height-1 do
    begin
      fsrgb:=sbmp.ScanLine[y];
      fdrgb:=dbmp.ScanLine[y];
      for x := 0 to sbmp.Width-1 do
      begin
        frgb.R:=fsrgb[x].R;
        frgb.G:=fsrgb[x].G;
        frgb.B:=fsrgb[x].B;
        fhsv:=RGBtoHSV(frgb);
        //色相(Hue 0~360°)の変換範囲を求める
        //ScrollBar1.Position ± ScrollBar2.Positionの範囲
        h1:=ScrollBar1.Position-ScrollBar2.Position;
        h2:=ScrollBar1.Position+ScrollBar2.Position;
        h11:=h1;
        h12:=h2;
        h21:=-1;
        h22:=-1;
        if h1<0 then
        begin
          h11:=h1+360;
          h12:=360;
          h21:=0;
          h22:=h2;
        end
        else if h2>=360 then
        begin
          h11:=h1;
          h12:=360;
          h21:=0;
          h22:=h2-360;
        end;
        //色相(Hue 0~360°)の変換範囲内のピクセルのみ色相を変換する
        if ((fhsv.H>=h11) and (fhsv.H<h12)) OR
           ((fhsv.H>=h21) and (fhsv.H<h22)) then
        begin
          //色相(Hue 0~360°)を変換する
          fhsv.H:=fhsv.H+(ScrollBar3.Position-ScrollBar1.Position);
          fhsv.H:=FMod(fhsv.H,360);
          frgb:=HSVtoRGB(fhsv);
          fdrgb[x].R:=frgb.R;
          fdrgb[x].G:=frgb.G;
          fdrgb[x].B:=frgb.B;
        end
        else
        begin
          //色相を変換しないピクセルの場合
          fdrgb[x].R:=fsrgb[x].R;
          fdrgb[x].G:=fsrgb[x].G;
          fdrgb[x].B:=fsrgb[x].B;
        end;
     end;
    end;
    Image5.Picture.Bitmap.Assign(dbmp);
  finally
    sbmp.Free;
    dbmp.Free;
  end;
  Image2.Invalidate;
  ScrollBar4Change(nil);
end;

procedure TForm1.DrawHue();
var i:Integer;
    frgb:TRGB;
    fhsv:THSV;
    hmax,hmin:Integer;
begin
  fhsv.S:=1;
  fhsv.V:=1;
  hmin:=ScrollBar1.Position-ScrollBar2.Position;
  hmax:=ScrollBar1.Position+ScrollBar2.Position;
  image2.Width:=hmax-hmin;
  image2.Picture.Bitmap.Width:=Image2.Width;
  for i := hmin to hmax do
  begin
    fhsv.H:=i;
    frgb:=HSVtoRGB(fhsv);
    //変換対象の色相の範囲を表示
    image2.Picture.Bitmap.Canvas.Pen.Color:=RGB(frgb.R,frgb.G,frgb.B);
    image2.Picture.Bitmap.Canvas.MoveTo(i-hmin,0);
    image2.Picture.Bitmap.Canvas.LineTo(i-hmin,Image2.Height);
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
var fhsv:THSV;
    frgb:TRGB;
    i:integer;
    bmp:TBitmap;
begin
  if Assigned(Image4.Picture.Graphic) then
  begin
    bmp:=TBitmap.Create;
    try
      bmp.Assign(Image4.Picture.Graphic);
      Image4.Picture.Bitmap.Assign(bmp);
    finally
      bmp.Free;
    end;
  end;

  //変換対象の色相中心(度)
  ScrollBar1.Width:=359+20*2;
  ScrollBar1.Max:=359;
  ScrollBar1.Min:=0;
  ScrollBar1.Position:=0;
  //変換対象の色相中心の幅(度)(変換対象色相中心 ± 幅 が対象)
  ScrollBar2.Width:=180+20*2;
  ScrollBar2.Max:=180;
  ScrollBar2.Min:=1;
  ScrollBar2.Position:=10;
  //変換後の色相中心(度)(変換後色相中心 ± 幅 に色相を変換する)
  ScrollBar3.Width:=359+20*2;
  ScrollBar3.Max:=359;
  ScrollBar3.Min:=1;
  ScrollBar3.Position:=30;
  //画像の表示倍率
  ScrollBar4.Width:=100;
  ScrollBar4.Min:=10;
  ScrollBar4.Max:=200;
  ScrollBar4.Position:=50;

  //変換元画像表示
  Image4.Left:=0;
  Image4.Top:=0;
  Image4.Proportional:=True;
  Image4.Stretch:=True;
  Image4.Cursor:=crCross;

  //変換した画像表示用
  Image5.Left:=0;
  Image5.Top:=0;
  Image5.Proportional:=True;
  Image5.Stretch:=True;
  Image5.Picture.Bitmap.PixelFormat:=pf24bit;

  //色相(0~359°)中心の表示用
  Image1.Width:=360;
  Image1.Height:=8;
  Image1.Picture.Bitmap.Width:=360;
  Image1.Picture.Bitmap.Height:=8;
  Image1.Picture.Bitmap.PixelFormat:=pf24bit;
  //変換後の色相(色相中心±幅) 表示用
  Image3.Width:=360;
  Image3.Height:=8;
  Image3.Picture.Bitmap.Width:=360;
  Image3.Picture.Bitmap.Height:=8;
  Image3.Picture.Bitmap.PixelFormat:=pf24bit;
  ////色相(Hue 0~360°)の描画
  fhsv.S:=1;
  fhsv.V:=1;
  for i := 0 to 359 do
  begin
    fhsv.H:=i;
    frgb:=HSVtoRGB(fhsv);
    image1.Picture.Bitmap.Canvas.Pen.Color:=RGB(frgb.R,frgb.G,frgb.B);
    image1.Picture.Bitmap.Canvas.Pen.Style:=psSolid;
    image1.Picture.Bitmap.Canvas.MoveTo(i,0);
    image1.Picture.Bitmap.Canvas.LineTo(i,image3.Height);
    image3.Picture.Bitmap.Canvas.Pen.Color:=RGB(frgb.R,frgb.G,frgb.B);
    image3.Picture.Bitmap.Canvas.Pen.Style:=psSolid;
    image3.Picture.Bitmap.Canvas.MoveTo(i,0);
    image3.Picture.Bitmap.Canvas.LineTo(i,image3.Height);
  end;

  //変換対象の色相幅(色相中心±幅)表示用
  Image2.Height:=8;
  Image2.Picture.Bitmap.Height:=Image1.Height;

  DrawHue();
  ScrollBar4Change(nil);
end;

procedure TForm1.Image4MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var col:TColor;
    frgb:TRGB;
    fhsv:THSV;
    pt:TPoint;
begin
  //元画像でマウスボタンが押されたとき、
  //マウスダウンした対象ピクセルの色相(0~359度)を取得して
  //ScrollBar1.Positionに設定する
  try
    pt.X:=X*100 div ScrollBar4.Position;
    pt.Y:=Y*100 div ScrollBar4.Position;
    col:=Image4.Picture.Bitmap.Canvas.Pixels[pt.X,pt.Y];
    frgb.R:=((col shr  0) and $FF);
    frgb.G:=((col shr  8) and $FF);
    frgb.B:=((col shr 16) and $FF);
    fhsv:=RGBtoHSV(frgb);
    ScrollBar1.Position:=Round(fhsv.H);
  finally
  end;
end;

procedure TForm1.ScrollBar1Change(Sender: TObject);
begin
  DrawHue();
end;

procedure TForm1.ScrollBar2Change(Sender: TObject);
begin
  DrawHue();
end;

procedure TForm1.ScrollBar4Change(Sender: TObject);
begin
  //画像の拡大縮小スクロールバーの値が変更されたとき
  if Assigned(Image4.Picture.Bitmap) then
  begin
    Image4.Width :=Image4.Picture.Bitmap.Width *ScrollBar4.Position div 100;
    Image4.Height:=Image4.Picture.Bitmap.Height*ScrollBar4.Position div 100;
  end;
  if Assigned(Image5.Picture.Bitmap) then
  begin
    Image5.Width :=Image5.Picture.Bitmap.Width *ScrollBar4.Position div 100;
    Image5.Height:=Image5.Picture.Bitmap.Height*ScrollBar4.Position div 100;
  end;
end;

end.

実行する

実行ボタンを押して実行します。(デバッグ実行でもOK)

Image4の色相を変換したいピクセルをクリックします。
ScrollBar3をドラッグして変換したい色相に設定します。
Button1をクリックすると、Image4画像の指定色相範囲を変換してImage5に表示します。