マウスカーソルの形状も含めてデスクトップ画面をキャプチャしたくなりました!みたいな時は、もしかしたら『コレ』が使えるカモ?しれません・・・②

“Say Hello to Capity Plus.” A Lightweight screen capture utility

上の図のように、マウスカーソルの形状も含めてキャプチャできます!
範囲の選択には、矩形に加え、正方形/楕円/正円も使えるようになりました!


画像編集に際し、自分が欲しいと思う必要最低限の機能のみを実装したプログラムを前回アップロードし、その紹介記事で次のように書きましたが・・・

『このアプリは本格的な画像編集に使用するための素材、もしくは、操作方法の解説を作成するために必要な情報画像(部分的な切り抜き画像)を簡単に作成したいという目的を実現するために開発しました。ですので「現在、表示されている画面の全部、もしくは一部を、必要であればマウスカーソルの形状を含めた画像データとして取得する」ことしかできません。保存した画像データを再度読み込んで表示したり、キャプチャした画像を加工する(例えば、ぼかす・モザイクをかけるといったような)機能はありません。ただし、画像の指定範囲を「ぼかす・モザイクをかける」機能は、後日、追加できたら、追加したいと考えています。』

今回、「ぼかす・モザイクをかける」といった機能に加え、既存の画像ファイルを読み込んで表示したり、アルファチャンネルを用いた透明化処理を PNG 形式の画像処理に追加するといった、自分では使わないかな? と思う機能も搭載した新しいバージョンができましたので紹介させていただきます。

【もくじ】

0.基本的な使い方と名称について(前回の記事 Plus α)
(1)起動方法
(2)キャプチャ方法
(3)操作パネルの位置の変更
(4)ラバーバンド形状
(5)処理一覧
 ・名称について
1.追加機能①「開く」
2.追加機能②「円形選択と保存・送信を可能に」
3.追加機能③「ぼかし処理」
4.追加機能④「モザイク処理」
5.追加機能⑤「白色化処理」
6.プログラムのダウンロード
7.まとめ
8.お願いとお断り

0.基本的な使い方と名称について

(1)起動方法

このアイコンをダブルクリックして起動します。

現在表示されているデスクトップ画面(の一部)をキャプチャするのが、このプログラムの主たる目的なので、メイン画面は起動時には表示されません。

起動時の画面
トースト通知(Toast Notification)の表示は Windows の設定により、出ない場合もあります。


元々、このプログラムを作ろうと思ったいちばんの理由は、『マウスカーソルの形状を含めて画面をキャプチャする必要が生じ、探した範囲では手軽に使えるアプリが見つからなかったので、それなら自分で書こうと思った』ことです。なので、この機能をいちばん最初に実装しました。

チェックボックスをチェックすればカーソルも含めて画面をキャプチャできます。

(2)キャプチャ方法

ショートカットキー「Shift+Ctrl+C」で現在表示されている画面全体をキャプチャできます(画面を指定してキャプチャすることはできません)。キャプチャした画像は「静かに」プログラムのメイン画面へと送られ(表示され)ます。その際、メッセージ等は何も表示されません。

キャプチャ後、タスクバーにあるオレンジ色のアイコンをクリックすると、メイン画面が表示されます。

もちろん、自分自身のキャプチャも可能です。
画面右側にキャプチャした画像のサムネイルが表示されます。


(3)操作パネルの位置の変更

上の図に示したように、操作パネルは「メイン画面の上部/下部」いずれかへの配置を選択できるようにしました。

画面上部に操作パネルを表示する場合です。
(設定は即適用&自動的に保存され、次回起動時に適用されます)


(4)ラバーバンド形状

ラバーバンドの形は、円形も選択できるようにしました。Shiftキーを押しながらドラッグすることで、矩形を選択している場合は「四角形 → 正方形」、円形を選択している場合は「楕円 → 正円」へとラバーバンドの形状が変化します。なお、いったん、四角形(長方形)や楕円のラバーバンドを描画し、その後、ラバーバンドのグラブハンドルをクリックしてリサイズする場合も、Shiftキーを押しながら操作すると、ラバーバンドの形状は「四角形 → 正方形」or 「楕円 → 正円」へ変化します。

ラバーバンドの形状は「矩形」or「円形」いずれかを選択できます。


ラバーバンドの線については、太さと色を指定できます。プログラムは、終了時の設定を自動的に記録し、次回起動時は前回終了時の設定を読み込んで(=復元して)起動します。

線の太さは10段階で指定可能です。


色は、TColorBox のデフォルトの設定色3種類から選べます。

上記3種類にチェックがある場合、
2つ上の図の TColorBox には184色が選択可能な Item として設定されました。


(5)処理一覧

あとは、「画像をそのまま保存」したり、「矩形/正方形/楕円/正円のいずれかのラバーバンドでさらにキャプチャしたい範囲を選択して、選択範囲内で右クリックすると表示されるメニューから選択できる処理を選んで実行する」ことが可能です。

このような解説画面を『とにかく簡単に』作りたくて作ったのが Capity です!


処理可能な画像数は、正直、自分でもよくわかりません。お使いの PC 環境(搭載しているメモリの大きさ等)により変化するものと思われます。保存するファイルの名称は、もちろん任意の名称を付けることも可能ですが、デフォルト設定では「 Screenshot_20251005_032342.png 」のように Screenshot_ に続けて西暦年月日時分秒が自動的に付くので、これまでファイルとして保存する際に面倒に感じていた「名前を付ける」作業から完全に解放されました。作った自分で言うのもナンですが、すごく便利です!!

・名称について

Capity という名称は、こちらも前回の記事で、『 AI に相談して決めた!』と書きましたが、その際 AI が示してくれたのが次の内容です。

・発音が柔らかく親しみやすい。技術系にも一般向けにも通用する響き。
・Capture + Simplicity / Utility / Clarity などの抽象的な価値を含められる造語。
・「City(都市)」や「Clarity(明快さ)」にも近い響きがあり、好ましい印象を与える。
・「キャプチャの能力(Capacity)」を連想させることもでき、機能性の高さを暗示。
・ロゴ・UI・ドメイン名・SNS ハンドルなどにも使いやすく、拡張性が高い。

それがほんとうか、どうかは使ってくださった方のお気持ち次第ではありますが・・・ 自分的には、この AI が示してくれた内容を具現化したプログラムになるよう、精一杯努力したつもり・・・です。

もちろん「特許情報プラットフォーム J-Plat Pat」で、特許・実用新案、意匠、商標の各権利について過去に、この名称での申請・登録のないことは確認済みです。(2025年10月5日現在)

1.追加機能①「開く」

最初は既存の画像を開く処理は不要と考え、実装していませんでしたが、簡単に実装できますし、「ない」よりは「ある」方がいいかと思い直して実装しました。ただ、あくまでもこのプログラムは、「現在表示されている画面を簡単にキャプチャする」ことが主な目的なので、ボタンの位置は深く考えずにほとんどおまけ程度に実装しましたので、ボタン自体の使い勝手はよくないと思います・・・。

ボタンクリックで TOpenDialog が表示されます。


ファイルを開く場合の Path の設定は、TOpenDialog の機能まかせ(=Windows まかせ)です。前回使用したフォルダが記憶されていれば、そのフォルダが自動的に選択されます。

表示したい画像を選択して、「開く」をクリックしてください。


表示された画像に対して、必要な処理を適用してください。

2.追加機能②「円形選択と保存・送信を可能に」

範囲を選択するのに使うラバーバンドは、矩形に加え、円形の形状をしたものも使えるようにしました。さらに Shift キーを押しながらドラッグすることにより、正方形や正円も描画できます。

目的に応じて使い分けてください。
(この画像も自分自身で作成しました)


(1)円形選択時の保存処理

画像の保存について解説します。ラバーバンドの形状が矩形・円形のいずれであっても、画像の保存形式は BMP ・ PNG ・ JPEG から選んで1種類を指定できます。

デフォルト設定は PNG 形式ですが、ここでは JPEG 形式が選ばれています。


キャプチャした画像の一部をラバーバンドで範囲選択し、選択した範囲内の任意の位置を右クリックすると次のようなサブメニューが表示されます。ここでは、まず、保存の処理から順に説明します。

表示されるメニューのコマンドは、すべてラバーバンド内のみに限って適用されます。


ラバーバンドが円形の場合、画像の保存時には注意が必要です。ラバーバンドの枠の内部の画像のみ保存対象とするのは、画像の形式によらず共通ですが、BMPとJPEG形式で保存する場合、枠外部分の透明化処理は行われず、枠外の部分は「白に塗りつぶされ」て保存されます。

「いいえ」をクリックした場合は、保存の処理自体がキャンセルされます。


保存された画像(例:BMP形式の場合)です。

青の部分がきれいに保存されています。


保存された画像(例:JPEG形式の場合)です。

圧縮処理が行われたため、青や緑が濃くなっています。


PNG 形式で保存する場合、次のメッセージが表示されます。用途に応じてラバーバンドの枠外の部分を「透明化する」もしくは「白く塗りつぶす」いずれかの処理を選択できます。

使用目的に合わせて処理を選択してください。


PNG 形式かつ「透明化あり」で保存した画像をフォトで見た場合です。

透明化した部分は黒くなっています。

同じ形式で「透明化なし・枠外を白く塗りつぶして保存」した場合の画像をフォトで見ると・・・

こちらは枠外の部分が黒くなっていません(当然ですが)


PNG 形式かつ「透明化あり」で保存した画像をパワーポイントに挿入してみました!

ラバーバンドの枠外が透明化されています。


同じ画像をWordに挿入してみました。

いったん保存してから挿入を行った結果です。


このように、PNG 形式かつ「透明化あり」で保存した画像は、「挿入」することで透明化処理が適用された状態で再利用できます。

(2)円形選択時のクリップボードへの送信

次に、クリップボードへの送信について説明します。

ラバーバンド内を右クリックして表示されるメニューから
「クリップボードへ送る」をクリックしてください。


ラバーバンドの枠が円形指定で、さらに範囲選択した部分を PNG 形式でクリップボードへ送信する場合、次のメッセージが表示されます。


「はい」を選んだ場合、例えば古いお絵描きソフトで背景色「黒」の画像を新規に作成しておいて、そこに円形(楕円)選択した範囲をクリップボードへ送信して(クリップボード経由で)貼り付けてみました。なお、このような場合には「背景色を透過色として貼り付ける(と同等の機能を利用して実行する)」必要があります。

思い出せないくらい、ながーい間愛用しているお絵描きソフトに
「背景色を透過色として」貼り付けてみました。


こちらが貼り付けた結果です。


背景が白の画像を円形で範囲選択して、背景が白の画像にクリップボード経由で貼り付けると困ったことになりますので、注意してください。

黒い字の部分が読めなくなってしまいます・・・


BMP や JPEG 形式を選択してクリップボードへ送信した場合は、次のメッセージが表示されます。


BMP 形式を選択し、表示されたメッセージの「はい」を選択して、クリップボードへ送信したデータを Word に貼り付けてみました。

ヒトの顔の切り抜きとか、そういう用途には使えるカモしれません。


この円形のラバーバンドに関する処理は、矩形時のそれにくらべると、要した時間は3倍以上かかっていると思います。とにかくない袖にタオルと雑巾を付け足して作った袖を振り回し、なんでもいいや、とにかくすーぱー頑張って作成しましたが、自分自身がこの円形のラバーバンドを使用する機会は今回限りであるような気が・・・。どこかで、どなたさまかのお役に立ってほしいと切に祈ります。

3.追加機能③「ぼかし処理」

ほんとうのことを言うと、円形のラバーバンドよりこちらを先に作成したのですが、出来たら実装したかった機能のひとつがこの「ぼかし処理」です。より低速になるのはわかりきっていましたが、搭載するならボックスブラーではなく、ガウシアンブラーと決めていました。

理由はただひとつ。少しくらい遅くても、「美しさ」を優先したかったのです。

ぼかす元画像です。


Box ぼかしを適用した画像です。

速いのですが、どうしてもジャギーな感じになります。

レベルは 10 まで指定できます。


ガウスぼかしを適用した画像です。

ごく自然な感じでぼかせます。
(レベルは5です)


文字にもガウスぼかしをかけてみました。レベルは5です。

ぼかす前の文字のある画像
ラバーバンドの内部をぼかした画像


ガウスぼかしとボックスぼかしのコードのセットです。ガウスぼかしのコードの↓に、ボックスぼかしのコードがあります。Boxぼかしに変更するときは、ガウスぼかしの変数はそのまま、var 宣言部の count 変数だけコメントアウトを解除してください。コード部分は、ガウスぼかしのコードをすべてコメント化して、Box ぼかしのコメントアウトを解除してください。

procedure TForm1.HandleGaussianBlur(Sender: TObject; const SelRectOnParent: TRect);
var
  //ガウスぼかし
  bmpSrc, bmpTemp: TBitmap;
  x, y, dx, dy, i, j: Integer;
  //固定小数点演算のため Int64 を使用 (合計値が非常に大きくなるため)
  r, g, b: Int64;
  radius: Integer;
  //カーネルを固定小数点値 (Int32) の配列として定義し直す
  kernel: array of Int32;
  pSrc, pTemp: PByteArray;
  blurLevel: Integer;
  selRectLocal: TRect;

  cx, cy, rx, ry: Double;
  IsEllipse: Boolean;

  //Box ぼかし
  //count: integer;  //Box ぼかしをかける場合はここのコメントアウトを解除する

  //ガウスカーネル生成関数(固定小数点対応版)
  function CreateGaussianKernel(radius: Integer; var kernel: array of Int32): Double;
  var
    k: array of Double; //一時的に浮動小数点カーネルを作成
    sigma, sum: Double;
    i: Integer;
  begin
      //浮動小数点カーネルの計算
      SetLength(k, radius * 2 + 1);
    sigma := radius / 2.0;
    sum := 0.0;
    for i := -radius to radius do
    begin
      k[i + radius] := Exp(-Sqr(i) / (2 * Sqr(sigma)));
      sum := sum + k[i + radius];
    end;

    //正規化と固定小数点へのスケーリング
    for i := 0 to High(k) do
      //正規化して SCALE_VALUE を乗算し、整数に丸める
      kernel[i] := Round((k[i] / sum) * SCALE_VALUE);
    Result := sigma;
  end;

  function IsInsideEllipse(x, y: Integer): Boolean;
  var
    dx, dy: Double;
  begin
    dx := (x - cx) / rx;
    dy := (y - cy) / ry;
    Result := (dx * dx + dy * dy) <= 1.0;
  end;

begin

  //ガウスぼかし
  if not Assigned(imgPreview.Picture.Graphic) then Exit;

  PushUndo;

  selRectLocal.TopLeft :=
    imgPreview.ScreenToClient(plImage1.Parent.ClientToScreen(SelRectOnParent.TopLeft));
  selRectLocal.BottomRight :=
    imgPreview.ScreenToClient(plImage1.Parent.ClientToScreen(SelRectOnParent.BottomRight));

  Screen.Cursor := crHourGlass;

  bmpSrc := TBitmap.Create;
  try
    bmpSrc.PixelFormat := pf24bit;
    bmpSrc.SetSize(imgPreview.Picture.Width, imgPreview.Picture.Height);
    bmpSrc.Canvas.Draw(0, 0, imgPreview.Picture.Graphic);

    bmpTemp := TBitmap.Create;
    try
      bmpTemp.PixelFormat := pf24bit;
      bmpTemp.SetSize(bmpSrc.Width, bmpSrc.Height);

      blurLevel := TrackBar1.Position;
      radius := EnsureRange(blurLevel, 1, 10);
      SetLength(kernel, radius * 2 + 1);
      CreateGaussianKernel(radius, kernel);

      IsEllipse := (RadioGroup1.ItemIndex = 1);
      if IsEllipse then
      begin
        cx := (selRectLocal.Left + selRectLocal.Right) / 2;
        cy := (selRectLocal.Top + selRectLocal.Bottom) / 2;
        rx := (selRectLocal.Right - selRectLocal.Left) / 2;
        ry := (selRectLocal.Bottom - selRectLocal.Top) / 2;
      end;

      // 横方向ブラー
      for y := selRectLocal.Top to selRectLocal.Bottom - 1 do
      begin
        pSrc := bmpSrc.ScanLine[y];
        pTemp := bmpTemp.ScanLine[y];
        for x := selRectLocal.Left to selRectLocal.Right - 1 do
        begin
          if IsEllipse and not IsInsideEllipse(x, y) then
          begin
            pTemp[x * 3 + 2] := pSrc[x * 3 + 2];
            pTemp[x * 3 + 1] := pSrc[x * 3 + 1];
            pTemp[x * 3 + 0] := pSrc[x * 3 + 0];
            Continue;
          end;

          r := 0; g := 0; b := 0;
          for dx := -radius to radius do
          begin
            i := EnsureRange(x + dx, 0, bmpSrc.Width - 1);
            r := r + Int64(pSrc[i * 3 + 2]) * kernel[dx + radius];
            g := g + Int64(pSrc[i * 3 + 1]) * kernel[dx + radius];
            b := b + Int64(pSrc[i * 3 + 0]) * kernel[dx + radius];
          end;
          pTemp[x * 3 + 2] := Byte(r shr SCALE_SHIFT);
          pTemp[x * 3 + 1] := Byte(g shr SCALE_SHIFT);
          pTemp[x * 3 + 0] := Byte(b shr SCALE_SHIFT);
        end;
      end;

      // 縦方向ブラー
      for x := selRectLocal.Left to selRectLocal.Right - 1 do
      begin
        for y := selRectLocal.Top to selRectLocal.Bottom - 1 do
        begin
          if IsEllipse and not IsInsideEllipse(x, y) then
            Continue;

          r := 0; g := 0; b := 0;
          for dy := -radius to radius do
          begin
            j := EnsureRange(y + dy, 0, bmpSrc.Height - 1);
            pTemp := bmpTemp.ScanLine[j];
            r := r + Int64(pTemp[x * 3 + 2]) * kernel[dy + radius];
            g := g + Int64(pTemp[x * 3 + 1]) * kernel[dy + radius];
            b := b + Int64(pTemp[x * 3 + 0]) * kernel[dy + radius];
          end;
          pSrc := bmpSrc.ScanLine[y];
          pSrc[x * 3 + 2] := Byte(r shr SCALE_SHIFT);
          pSrc[x * 3 + 1] := Byte(g shr SCALE_SHIFT);
          pSrc[x * 3 + 0] := Byte(b shr SCALE_SHIFT);
        end;
      end;

      imgPreview.Canvas.CopyRect(selRectLocal, bmpSrc.Canvas, selRectLocal);

    finally
      bmpTemp.Free;
    end;
  finally
    bmpSrc.Free;
    Screen.Cursor := crDefault;
  end;


  //BoxBlurを試す場合は、上のガウスぼかしのコードをすべてコメントアウトする
  //BoxBlur
  {
  if not Assigned(imgPreview.Picture.Graphic) then Exit;

  PushUndo;

  selRectLocal.TopLeft :=
    imgPreview.ScreenToClient(plImage1.Parent.ClientToScreen(SelRectOnParent.TopLeft));
  selRectLocal.BottomRight :=
    imgPreview.ScreenToClient(plImage1.Parent.ClientToScreen(SelRectOnParent.BottomRight));

  Screen.Cursor := crHourGlass;

  bmpSrc := TBitmap.Create;
  try
    bmpSrc.PixelFormat := pf24bit;
    bmpSrc.SetSize(imgPreview.Picture.Width, imgPreview.Picture.Height);
    bmpSrc.Canvas.Draw(0, 0, imgPreview.Picture.Graphic);

    bmpTemp := TBitmap.Create;
    try
      bmpTemp.PixelFormat := pf24bit;
      bmpTemp.SetSize(bmpSrc.Width, bmpSrc.Height);

      blurLevel := TrackBar1.Position;
      radius := EnsureRange(blurLevel, 1, 10);

      IsEllipse := (RadioGroup1.ItemIndex = 1);
      if IsEllipse then
      begin
        cx := (selRectLocal.Left + selRectLocal.Right) / 2;
        cy := (selRectLocal.Top + selRectLocal.Bottom) / 2;
        rx := (selRectLocal.Right - selRectLocal.Left) / 2;
        ry := (selRectLocal.Bottom - selRectLocal.Top) / 2;
      end;

      // 横方向ボックスぼかし
      for y := selRectLocal.Top to selRectLocal.Bottom - 1 do
      begin
        pSrc := bmpSrc.ScanLine[y];
        pTemp := bmpTemp.ScanLine[y];
        for x := selRectLocal.Left to selRectLocal.Right - 1 do
        begin
          if IsEllipse and not IsInsideEllipse(x, y) then
          begin
            pTemp[x * 3 + 2] := pSrc[x * 3 + 2];
            pTemp[x * 3 + 1] := pSrc[x * 3 + 1];
            pTemp[x * 3 + 0] := pSrc[x * 3 + 0];
            Continue;
          end;

          r := 0; g := 0; b := 0;
          count := 0;
          for dx := -radius to radius do
          begin
            i := EnsureRange(x + dx, 0, bmpSrc.Width - 1);
            r := r + pSrc[i * 3 + 2];
            g := g + pSrc[i * 3 + 1];
            b := b + pSrc[i * 3 + 0];
            Inc(count);
          end;
          pTemp[x * 3 + 2] := Byte(r div count);
          pTemp[x * 3 + 1] := Byte(g div count);
          pTemp[x * 3 + 0] := Byte(b div count);
        end;
      end;

      // 縦方向ボックスぼかし
      for x := selRectLocal.Left to selRectLocal.Right - 1 do
      begin
        for y := selRectLocal.Top to selRectLocal.Bottom - 1 do
        begin
          if IsEllipse and not IsInsideEllipse(x, y) then
            Continue;

          r := 0; g := 0; b := 0;
          count := 0;
          for dy := -radius to radius do
          begin
            j := EnsureRange(y + dy, 0, bmpSrc.Height - 1);
            pTemp := bmpTemp.ScanLine[j];
            r := r + pTemp[x * 3 + 2];
            g := g + pTemp[x * 3 + 1];
            b := b + pTemp[x * 3 + 0];
            Inc(count);
          end;
          pSrc := bmpSrc.ScanLine[y];
          pSrc[x * 3 + 2] := Byte(r div count);
          pSrc[x * 3 + 1] := Byte(g div count);
          pSrc[x * 3 + 0] := Byte(b div count);
        end;
      end;

      imgPreview.Canvas.CopyRect(selRectLocal, bmpSrc.Canvas, selRectLocal);

    finally
      bmpTemp.Free;
    end;
  finally
    bmpSrc.Free;
    Screen.Cursor := crDefault;
  end;
  }
end;

4.追加機能④「モザイク処理」

もうひとつ、出来たら実装したかったのが指定範囲に「モザイクをかける」処理です。文字情報を隠す用途であれば、強くぼかし処理する(or 重ね掛けする)だけで十分な気もしましたが、私には「ぼかす」+「モザイクをかける」の二手間を1セットにして文字情報を隠したい場合に画像を処理するクセがあり(このブログの過去記事を見ていただければ理解していただけると思います)、今回も2つで1セットのような気がして・・・。

モザイク処理する元画像です。


とりあえず、レベル5を設定して・・・

レベルは10まであります。


モザイク処理してみた結果です。ボックスぼかしみたいですね。なのでボックスぼかしは実装しませんでした。


文字をモザイク処理してみました。レベルは5です。

モザイクをかける前の画像
モザイク処理したラバーバンド内は、色の違いしか、わからなくなりました!


モザイクをかける処理のコードです。ご参考まで。

procedure TForm1.HandlePixelation(Sender: TObject; const SelRectOnParent: TRect);
var
  selRectLocal: TRect;
  bmpSrc: TBitmap;
  startX, startY: Integer;
  dx, dy: Integer;
  blockSize: Integer;
  r, g, b, count: Integer;
  pLineRead, pLineWrite: PByteArray;
  BlockWidth, BlockHeight: Integer;
  cx, cy, rx, ry: Double;
  IsEllipse: Boolean;

  function IsInsideEllipse(x, y: Integer): Boolean;
  var
    dx, dy: Double;
  begin
    dx := (x - cx) / rx;
    dy := (y - cy) / ry;
    Result := (dx * dx + dy * dy) <= 1.0;
  end;

begin

  if not Assigned(imgPreview.Picture.Graphic) then Exit;

  PushUndo;

  selRectLocal.TopLeft :=
    imgPreview.ScreenToClient(plImage1.Parent.ClientToScreen(SelRectOnParent.TopLeft));
  selRectLocal.BottomRight :=
    imgPreview.ScreenToClient(plImage1.Parent.ClientToScreen(SelRectOnParent.BottomRight));

  bmpSrc := TBitmap.Create;
  Screen.Cursor := crHourGlass;

  try
    bmpSrc.PixelFormat := pf24bit;
    bmpSrc.SetSize(imgPreview.Picture.Width, imgPreview.Picture.Height);
    bmpSrc.Canvas.Draw(0, 0, imgPreview.Picture.Graphic);

    blockSize := EnsureRange(TrackBar2.Position, 2, 50);

    IsEllipse := (RadioGroup1.ItemIndex = 1);
    if IsEllipse then
    begin
      cx := (selRectLocal.Left + selRectLocal.Right) / 2;
      cy := (selRectLocal.Top + selRectLocal.Bottom) / 2;
      rx := (selRectLocal.Right - selRectLocal.Left) / 2;
      ry := (selRectLocal.Bottom - selRectLocal.Top) / 2;
    end;

    startY := selRectLocal.Top;
    while startY < selRectLocal.Bottom do
    begin
      startX := selRectLocal.Left;
      while startX < selRectLocal.Right do
      begin
        r := 0; g := 0; b := 0; count := 0;

        BlockWidth := blockSize;
        if startX + BlockWidth > selRectLocal.Right then
          BlockWidth := selRectLocal.Right - startX;

        BlockHeight := blockSize;
        if startY + BlockHeight > selRectLocal.Bottom then
          BlockHeight := selRectLocal.Bottom - startY;

        //平均色の計算(楕円内のみ)
        for dy := 0 to BlockHeight - 1 do
        begin
          pLineRead := PByteArray(bmpSrc.ScanLine[startY + dy]);
          for dx := 0 to BlockWidth - 1 do
          begin
            if IsEllipse and not IsInsideEllipse(startX + dx, startY + dy) then
              Continue;

            r := r + pLineRead[(startX + dx) * 3 + 2];
            g := g + pLineRead[(startX + dx) * 3 + 1];
            b := b + pLineRead[(startX + dx) * 3 + 0];
            Inc(count);
          end;
        end;

        if count > 0 then
        begin
          r := r div count;
          g := g div count;
          b := b div count;
        end;

        //平均色の適用(楕円内のみ)
        for dy := 0 to BlockHeight - 1 do
        begin
          pLineWrite := PByteArray(bmpSrc.ScanLine[startY + dy]);
          for dx := 0 to BlockWidth - 1 do
          begin
            if IsEllipse and not IsInsideEllipse(startX + dx, startY + dy) then
              Continue;

            pLineWrite[(startX + dx) * 3 + 2] := r;
            pLineWrite[(startX + dx) * 3 + 1] := g;
            pLineWrite[(startX + dx) * 3 + 0] := b;
          end;
        end;

        Inc(startX, blockSize);
      end;
      Inc(startY, blockSize);
    end;

    imgPreview.Canvas.CopyRect(selRectLocal, bmpSrc.Canvas, selRectLocal);

  finally
    bmpSrc.Free;
    Screen.Cursor := crDefault;
  end;

end;

ガウスぼかし + モザイク処理の結果です。レベルはどちらも5です。

未処理の画像です。
ラバーバンド内をガウスぼかし + モザイク処理(ともにレベルは5)

5.追加機能⑤「白色化処理」

つい、(不要なのに)マウスの形状を含めてキャプチャしちゃった! みたいな場合、役に立つかも・・・と考え、実装しました。私の場合、背景色は「白」であることが多いので、単に指定範囲を「白で塗りつぶす」処理です。

不要なマウスカーソルまでキャプチャしちゃった!


マウスカーソル部分を範囲選択して右クリック、メニューの「白色化」をクリックします。


不要なカーソルは消えました☆

これだけです!!!

【追記_20251006】

ぼかし加工やモザイク処理を行った画像データが保存できない不具合を修正しました。この修正に合わせて、ぼかし加工やモザイク処理、及び白色化等、画像データを加工した場合は、任意の段階で「保存」ではなく、メモリ上の表示用データに「反映」する機能を追加しました。具体的には、次の通りです。

次の図のように画像の一部を加工します。例:白色化

画面右のサムネイルには変更が反映されていません。


この状態で、画面右のサムネイルをクリックすると、画像に設定した変更内容はすべて破棄され、加工前の画像が表示(復元)されます。加工状態がサムネイルに反映されていない状態でのサムネイル・クリックは、「一気に元に戻す処理になる」とお考えください。

メモリ上の画像データの更新は「反映(英語表記は Apply)」ボタンをクリックすることで実行できます。

ちいさなボタンですが・・・


このボタンをクリックすることで、メモリ上の画像データが、メイン画面に表示されている加工した状態の画像データに更新されます。

画面右のサムネイルにも変更が反映されます。


このようにメモリ上のデータの更新処理をユーザー側に委ねることで、Undo / Redo の履歴操作とサムネイル更新が完全に分離され、処理の整合性が保たれるようにしました。

初期バージョンで作者の保存機能に関する確認作業が至らなかったため、ご迷惑をおかけした皆さまに、こころからお詫び申し上げます。誠に申し訳ありませんでした。

現在、未発見の不具合が見つかりました場合は、こちらで報告し、速やかに修正版を掲載いたしますので、万一、お使いいただける場合は、修正版のアップロードの有無にご注意いただけましたら幸いです。

【追記_20251007】

自分で使っていて(そのために作ったプログラムですが)、画面のキャプチャだけでなく、クリップボードに画像データがある場合、それをショートカットキー( Ctrl + V )で貼り付けて、さらに加工等できたらいいなぁ・・・ と思いましたので、本日即、実装しました。

ショートカットキーを利用して、このプログラムのメイン画面に貼り付け可能な画像形式は、BMP、PNG、JPEG、GIF、TIFF に加え、画像の読み込みに必要な外部ライブラリが導入されていれば、HEIC や WebP にも対応可能と思われます。※ すべての画像形式について、その読み込みの可否に関する動作検証は行っておりません。もし、クリップボードにあるデータが、このプログラムで読み込めない画像データであった場合には、その旨を伝えるメッセージが表示されます。

また、乱暴な言い方で申し訳ないのですが、プログラムのバージョン管理は、まったく考えておりませんでしたので、この下のダウンロードリンクからダウンロードできるものが最新版です。

このプログラムは EXE ファイル1つで単体動作します(プログラム終了時の VCL コントロールの諸設定は C:\Users\ユーザー名\AppData\Roaming\Capity\settings.ini に保存されますが、この ini ファイルはなくてもプログラムは動作します)。また、レジストリは一切汚しておりませんので、 EXE ファイルを上書きすれば、プログラムの更新作業は完了します。

【追記_20251009】

自分で使っていて欲しいと思った機能をさらに追加しました。それは、矢印キーによるラバーバンドの移動と、形状の微調整です。以下、具体的な操作方法です。

移動方法:ラバーバンドが選択(Visible)されている状態で、Shiftキー + Ctrlキー + 各矢印キー押し下げで、現在の幅と高さを保ったまま、押し下げた矢印キーの方向に1ピクセルずつ移動できます。

形状の微調整:Shiftキー + 右向き矢印キーで幅を1ピクセルずつ増加、Shiftキー + 左向き矢印キーで幅を1ピクセルずつ減少、Shiftキー + 上向き矢印キーで高さを1ピクセルずつ減少、Shiftキー + 下向き矢印キーで高さを1ピクセルずつ増加させることができます。

【追記_2025_1012-1025】

さらに複数の機能を追加しました。追加した機能は、次の通りです。

(1)起動時に自動消音

操作時の Beep 音が気になるので、起動時に自動消音するようにしました。

起動直前の音量設定は保存されており、終了時に復元されます。


(2)グレイスケール / セピア 色の画像に変換

表示されている画像を、Grayscale もしくは Sepia いずれかの画像へ変換できるようにしました。

変換元の画像


・グレースケールに変換

グレースケールに変換


・セピアに変換

Sepia を指定した場合は、その強度を 0.1 ~ 1.0 の範囲で設定できます。

セピア色変換の強度を指定


強度「1.0」で変換した例です。

セピア色に変換


(3)背景色の切り替え

表示する画像に応じて、背景色を「白/黒」のいずれかに指定できるようにしました。背景が「白」の画像を表示する際は、背景色に「黒」を指定すると画像境界が明確になります。

背景色は「白」を指定した状態です。


背景色が「白」だと、画像の境界がわからない場合があります。


背景色を「黒」に切り替えました。画像の境界が明瞭になり、範囲選択しやすくなります。


なお、背景色の設定は、他の設定同様、終了時の設定が自動的に保存され、次回起動時は前回終了時の設定が復元された状態で起動します。

(4)回転

操作パネルの「回転」部分に任意の値を指定して「▶」ボタンをクリックしてください。正の値で時計回り(右回り)、負の値で反時計回り(左回り)に表示されている画像が回転します。

回転元画像です。

回転角度は、時計回りに3°を指定


「▶」ボタンクリックで回転が行われます。


メモリ上のデータに反映するか・どうかの確認が行われます。「はい」で反映され、「いいえ」で「取り消し処理(UnDo)」が実行されます。


(5)拡大と縮小

拡大と縮小処理を行います。100.00 %が等倍で、それより小さい値を指定すれば縮小、大きな値を指定すれば拡大されます。

縮小率 50 %を指定


うっすらと、図の周囲に灰色の線が描画されますが、ご容赦ください。


回転と同様に、メモリ上のデータに反映するか・どうかの確認が行われます。「はい」で反映され、「いいえ」で「取り消し処理(UnDo)」が実行されます。


(7)枠線

枠線部分はそのままですが、色は色名の表示をカットし、選択できる色数も減らしました。


(8)グラブハンドルの非表示

表示/非表示を切り替える CheckBox を追加

ラバーバンドのみ描画する状態も選択できるようにしました。工夫次第で次のような色枠で囲んだ画像を作成することもできます。


(9)表示している画像全体を選択可能にしました。クリアボタンクリックで選択を解除できます。


その他 1025 のアップデートでは、ラバーバンド内を右クリックすると表示されるサブメニューの項目の Enabled を設定可能とし、起動直後は「取り消し」処理が選べないようにしました。

6.プログラムのダウンロード

今回の記事で紹介した PC の画面キャプチャを実行するプログラム Capity Plus 一式を以下からダウンロードできます。なお、ダウンロードとご使用にあたっては、免責事項及び使用条件への同意が必要です。免責事項及び使用条件の詳細は付属の License.txt をご覧ください。

なお、プログラムの初回起動時には、Windows Defender SmartScreen による警告画面が表示されます。この警告画面に関する詳細は、当 Blog の次の過去記事をご参照ください。

7.まとめ

初期バージョンに追加した機能のまとめです。

(1)既存の画像も開けるようになりました。
(2)ラバーバンドの形状に円形( Drag : 楕円/Shft & Drag : 正円)を追加しました。
(3)操作パネルの位置を上 or 下に設定できるようにしました。
(4)クリップボードへの送信機能を円形対応にバージョンアップしました。
(5)指定範囲に「ガウスぼかし」をかけることができるようになりました。
(6)指定範囲を「モザイク処理」できるようになりました。
(7)指定範囲を「白色化」できるようになりました。
(8)終了時設定を C:\Users\ユーザー名\AppData\Roaming\Capity\settings.ini に自動保存。
(9)ショートカットキー( Ctrl + V )でクリップボードにある画像データを貼り付け。
(10)画像の加工内容をメモリ上の画像データに反映する機能を追加。
(11)起動時にボリュームが0より大きい場合は、自動消音します(終了時に元に戻る)。
(12)グレイスケール / セピア 色の画像に変換できます。
(13)背景色の切り替えが可能になりました(白と黒のいずれかを指定)。
(14)画像の回転(時計回り・反時計回り)
(15)画像の拡大と縮小

機能の追加(20251019)

(16)グラブハンドルの表示/非表示の切り替えが可能になりました。

機能の追加(20251025)

(17)表示している画像全体を選択・選択解除

全体として、「操作パネル」では「現在、表示されている画像全体の加工・設定」を行い、画像上をマウスでドラッグすることで描画される「ラバーバンドの内側」を右クリックすると表示されるサブメニューのコマンド群は、「ラバーバンドで指定した範囲内のみを加工・設定」するものとお考えいただけましたら幸いです。

【重要】
元に戻す( Ctrl + Z )際には、表示されている画像の状態をメモリ上のデータに「反映」させるか・どうかを問うメッセージは表示されません。これは「どこまで元に戻すのか?」の判定を自動で実行(判断)することが不可能なためです。ですので、どのタイミングで処理を「確定」させるかは、ユーザーサイドにお任せする仕様としてあります。もちろん、処理を「反映」させても元に戻す処理はある程度効きますが、画像への加工内容をメモリ上のデータに「反映」させるタイミングはあくまでもユーザーサイドでの判断に委ねる仕様ですので、この点につきましては十分ご留意ください。

また、画像を多量に扱うプログラムの場合は、メモリリークが心配ですので、プロジェクト・ソースの CapityPlus.dpr ファイルに以下のように” ReportMemoryLeaksOnShutdown := True ”を設定して、プログラムの終了後、もし、メモリリークがあればその状況を表示するようにして(あくまでも、私がテストした範囲内に限ってのことではありますが)プログラムが正常終了した際にメモリリークが発生しないことを確認済みです。

program CapityPlus;
uses
  Vcl.Forms,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}

begin
  ReportMemoryLeaksOnShutdown := True;
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.

8.お願いとお断り

このサイトの内容を利用される場合は、自己責任でお願いします。記載した内容(プログラムを含む)を利用した結果、利用者および第三者に損害が発生したとしても、このサイトの管理者は一切責任を負えません。予め、ご了承ください。

作者が気がついた限りの範囲ではありますが、動作検証を行い、発生したエラー等の不具合は誠心誠意取り除いたつもりですが、まだまだ未発見のバグは必ずあると思います。もともと、このブログの記事を書くために開発したプログラムなので、今後も使用する中で発見できた不具合は、可能な限り速やかに修正して、こちらの記事で追加報告させていただきます。上記ダウンロードリンクからダウンロードできる版が最新版です。実行ファイル( exe )は単体で動作しますので、バージョンアップは旧版を削除して新版にするか、単に新版を旧版に上書きするだけで完了します。