{*******************************************************}
{                                                       }
{       Tocsg.Capture                                   }
{                                                       }
{       Copyright (C) 2022 kku                          }
{                                                       }
{*******************************************************}

unit Tocsg.Capture;

interface

uses
  System.SysUtils, Vcl.Graphics, Vcl.Imaging.jpeg, Winapi.Windows,
  System.Classes;

type
  TJPEGDataHelper = class helper for Vcl.Imaging.jpeg.TJPEGData
  public
    function GetJpgDataStream: TStream;
  end;

  TJPEGImageHelper = class helper for Vcl.Imaging.jpeg.TJPEGImage
  public
    function GetJpgStream: TStream;
  end;

function CaptureDesktopAsJPEG: TJPEGImage;
function CaptureDesktopAsJpegStream(aStream: TStream): Boolean;
function CaptureDesktopAsJpegFile(sPath: String): Boolean;
function CaptureDesktopAsBITMAP: Vcl.Graphics.TBitmap;
function CaptureWindowAsJPEG(hWindow: HWND): TJPEGImage;
function CaptureWindowAsJpegFile(hWindow: HWND; sPath: String): Boolean;
function CaptureWindowAsBitmap(hWindow: HWND; pxFormat: TPixelFormat = pf24bit): Vcl.Graphics.TBitmap;
function CaptureWindowClientAsJPEG(hWindow: HWND): TJPEGImage;

implementation

uses
  Tocsg.Safe, Tocsg.Exception, Vcl.Forms, Winapi.DwmApi;

const
  CaptureBlt = $40000000;

{ TJPEGDataHelper }

function TJPEGDataHelper.GetJpgDataStream: TStream;
begin
  with Self do
    Result := FData;
end;

{ TJPEGImageHelper }

function TJPEGImageHelper.GetJpgStream: TStream;
begin
// TJPEGImage.SaveToStream() 참조
  with Self do
  begin
    JPEGNeeded;
    if TJPEGData(FImage) <> nil then
      Result := TJPEGData(FImage).GetJpgDataStream
    else
      Result := nil;
  end;
end;

function CaptureDesktopAsJPEG: TJPEGImage;
var
  DC: HDC;
  BM: Vcl.Graphics.TBitmap;
  nLeft, nTop,
  nWidth, nHeight: Integer;
begin
  DC := 0;
  BM := nil;
  Result := nil;

  try
  // GetDC에서 CreateDC로 변경
    DC := CreateDC('Display', nil, nil, nil);
//    DC := GetDC(0);
    if DC <> 0 then
    begin
      try
        nLeft := GetSystemMetrics(SM_XVIRTUALSCREEN);
        nTop := GetSystemMetrics(SM_YVIRTUALSCREEN);

        nWidth := GetSystemMetrics(SM_CXVIRTUALSCREEN);
        nHeight := GetSystemMetrics(SM_CYVIRTUALSCREEN);

  //      nWidth := GetDeviceCaps(DC, HORZRES);
  //      nHeight := GetDeviceCaps(DC, VERTRES);

//        Guard(BM, Vcl.Graphics.TBitmap.Create);
//        BM.Width := nWidth;
//        BM.Height := nHeight;
//
//        if BitBlt(BM.Canvas.Handle,
//                  0,
//                  0,
//                  nWidth,
//                  nHeight,
//                  DC,
//                  nLeft,
//                  nTop,
//                  SRCCOPY) then
//        begin
//          Result := TJPEGImage.Create;
//          Result.Assign(BM);
//        end;

        BM := Vcl.Graphics.TBitmap.Create;
        BM.Width := nWidth;
        BM.Height := nHeight;

        if BitBlt(BM.Canvas.Handle,
                  0,
                  0,
                  nWidth,
                  nHeight,
                  DC,
                  nLeft,
                  nTop,
                  SRCCOPY + CaptureBlt) then // CaptureBlt 추가, 윈도우 7 이하 투명 효과된 윈도우 캡쳐지원
        begin
          Result := TJPEGImage.Create;
          Result.Assign(BM);
        end;
      finally
        if DC <> 0 then
          DeleteDC(DC);
//          ReleaseDC(0, DC);
        if BM <> nil then //
          FreeAndNil(BM); //
      end;
    end;
  except
    on E: Exception do
    begin
//      ETgException.TraceException(E, Format('Fail .. CaptureDesktopAsJPEG(), sStep = %s', [sStep]));
      ETgException.TraceException(E, 'Fail .. CaptureDesktopAsJPEG()');
      Result := nil;
      exit;
    end;
  end;
end;

function CaptureDesktopAsJpegStream(aStream: TStream): Boolean;
var
  DC: HDC;
  BM: Vcl.Graphics.TBitmap;
  jpg: TJPEGImage;
  nLeft, nTop,
  nWidth, nHeight: Integer;
begin
  Result := false;
  DC := 0;
  BM := nil;
  jpg := nil;

  try
    try
    // GetDC에서 CreateDC로 변경
      DC := CreateDC('Display', nil, nil, nil);
//      DC := GetDC(0);
//      DC := GetDC(GetDesktopWindow);
      if DC <> 0 then
      begin
      // 듀얼도 전체 캡쳐되도록 변경
        nLeft := GetSystemMetrics(SM_XVIRTUALSCREEN);
        nTop := GetSystemMetrics(SM_YVIRTUALSCREEN);

        nWidth := GetSystemMetrics(SM_CXVIRTUALSCREEN);
        nHeight := GetSystemMetrics(SM_CYVIRTUALSCREEN);

        BM := Vcl.Graphics.TBitmap.Create;
        BM.Width := nWidth;
        BM.Height := nHeight;

        if BitBlt(BM.Canvas.Handle,
                  0,
                  0,
                  nWidth,
                  nHeight,
                  DC,
                  nLeft,
                  nTop,
                  SRCCOPY + CaptureBlt) then // CaptureBlt 추가, 윈도우 7 이하 투명 효과된 윈도우 캡쳐지원
        begin
          DeleteDC(DC);
//          ReleaseDC(0, DC);
          DC := 0;

          jpg := TJPEGImage.Create;
          jpg.Assign(BM);
          FreeAndNil(BM);

          jpg.SaveToStream(aStream);
          Result := true;
        end;
      end;
    except
      on E: Exception do
      begin
//        ETgException.TraceException(E, 'Fail .. CaptureDesktopAsJpegStream()');
        ETgException.TraceException(E, Format('Fail .. CaptureDesktopAsJpegStream(), Left = %d, Top = %d, Width = %d, Height = %d',
            [nLeft, nTop, nWidth, nHeight]));
        exit;
      end;
    end;
  finally
    if jpg <> nil then
      FreeAndNil(jpg);
    if BM <> nil then //
      FreeAndNil(BM); //
    if DC <> 0 then
      DeleteDC(DC);
//      ReleaseDC(0, DC);
  end;
end;

function CaptureDesktopAsJpegFile(sPath: String): Boolean;
var
  fs: TFileStream;
begin
  try
    Guard(fs, TFileStream.Create(sPath, fmCreate));
    Result := CaptureDesktopAsJpegStream(fs);
  except
    Result := false;
  end;
end;

function CaptureDesktopAsBITMAP: Vcl.Graphics.TBitmap;
var
  DC: HDC;
  nLeft, nTop,
  nWidth, nHeight: Integer;
begin
  DC := 0;
  Result := nil;

  try
  // GetDC에서 CreateDC로 변경
    DC := CreateDC('Display', nil, nil, nil);
//    DC := GetDC(0);
    if DC <> 0 then
    begin
      try
        nLeft := GetSystemMetrics(SM_XVIRTUALSCREEN);
        nTop := GetSystemMetrics(SM_YVIRTUALSCREEN);

        nWidth := GetSystemMetrics(SM_CXVIRTUALSCREEN);
        nHeight := GetSystemMetrics(SM_CYVIRTUALSCREEN);

        Result := Vcl.Graphics.TBitmap.Create;
        Result.Width := nWidth;
        Result.Height := nHeight;

        if not BitBlt(Result.Canvas.Handle,
                      0,
                      0,
                      nWidth,
                      nHeight,
                      DC,
                      nLeft,
                      nTop,
                      SRCCOPY + CaptureBlt) then // CaptureBlt 추가, 윈도우 7 이하 투명 효과된 윈도우 캡쳐지원
        begin
          FreeAndNil(Result);
        end;
      finally
        if DC <> 0 then
          DeleteDC(DC);
//          ReleaseDC(0, DC);
      end;
    end;
  except
    on E: Exception do
    begin
      ETgException.TraceException(E, 'Fail .. CaptureDesktopAsBMP()');
      Result := nil;
      exit;
    end;
  end;
end;

function CaptureWindowAsJPEG(hWindow: HWND): TJPEGImage;
var
  DC: HDC;
  RT: TRect;
  BM: Vcl.Graphics.TBitmap;
  bGetWindRect: Boolean;
begin
  Result := nil;

// GetDC에서 CreateDC로 변경
  DC := CreateDC('Display', nil, nil, nil);
//  DC := GetDC(hWindow);
  if DC <> 0 then
  begin
    ZeroMemory(@RT, SizeOf(TRect));

  // 기존 GetWindowRect()으로 창 크기를 가져오면 창테두리가 플랫하지 않고,
  // 무엇보다 Scale이 100% 이상일 경우 창 크기와 위치가 밀리는 문제가 있다.
  // 그래서 아래처럼 보완. DwmGetWindowAttribute()은 비스타, 서버 2008 이상만 지원함
    bGetWindRect := DwmGetWindowAttribute(hWindow, DWMWA_EXTENDED_FRAME_BOUNDS, @RT, SizeOf(RT)) = S_OK;
    if not bGetWindRect then
      bGetWindRect := GetWindowRect(hWindow, RT);

    if bGetWindRect then
    begin
      try
        Guard(BM, Vcl.Graphics.TBitmap.Create);
        BM.Width := RT.Width;
        BM.Height := RT.Height;

        if BitBlt(BM.Canvas.Handle,
                  0,
                  0,
                  RT.Width,
                  RT.Height,
                  DC,
                  RT.Left,
                  RT.Top,
                  SRCCOPY + CaptureBlt) then // CaptureBlt 추가, 윈도우 7 이하 투명 효과된 윈도우 캡쳐지원
        begin
          if (BM.Width + BM.Height) > 0 then
          begin
            Result := TJPEGImage.Create;
            Result.Assign(BM);
          end;
        end;
      except
        Result := nil;
        exit;
      end;
    end;
    DeleteDC(DC);
//    ReleaseDC(0, DC);
  end;
end;

function CaptureWindowAsJpegFile(hWindow: HWND; sPath: String): Boolean;
var
  jpg: TJPEGImage;
begin
  jpg := CaptureWindowAsJPEG(hWindow);
  if jpg <> nil then
  begin
    Result := true;
    jpg.SaveToFile(sPath);
    jpg.Free;
  end;
end;

function CaptureWindowAsBitmap(hWindow: HWND; pxFormat: TPixelFormat = pf24bit): Vcl.Graphics.TBitmap;
var
  DC: HDC;
  RT: TRect;
  bGetWindRect: Boolean;
begin
  Result := nil;

// GetDC에서 CreateDC로 변경
  DC := CreateDC('Display', nil, nil, nil);
//  DC := GetDC(0);
  if DC <> 0 then
  begin
    ZeroMemory(@RT, SizeOf(TRect));

  // 기존 GetWindowRect()으로 창 크기를 가져오면 창테두리가 플랫하지 않고,
  // 무엇보다 Scale이 100% 이상일 경우 창 크기와 위치가 밀리는 문제가 있다.
  // 그래서 아래처럼 보완. DwmGetWindowAttribute()은 비스타, 서버 2008 이상만 지원함
    bGetWindRect := DwmGetWindowAttribute(hWindow, DWMWA_EXTENDED_FRAME_BOUNDS, @RT, SizeOf(RT)) = S_OK;
    if not bGetWindRect then
      bGetWindRect := GetWindowRect(hWindow, RT);

    if bGetWindRect then
    begin
      try
        Result := Vcl.Graphics.TBitmap.Create;
        Result.Width := RT.Width;
        Result.Height := RT.Height;
        Result.PixelFormat := pxFormat;

        if not BitBlt(Result.Canvas.Handle,
                  0,
                  0,
                  RT.Width,
                  RT.Height,
                  DC,
                  RT.Left,
                  RT.Top,
                  SRCCOPY + CaptureBlt) then // CaptureBlt 추가, 윈도우 7 이하 투명 효과된 윈도우 캡쳐지원
        begin
          FreeAndNil(Result);
        end;
      except
        Result := nil;
        exit;
      end;
    end;
    DeleteDC(DC);
//    ReleaseDC(0, DC);
  end;
end;

function CaptureWindowClientAsJPEG(hWindow: HWND): TJPEGImage;
var
  DC: HDC;
  RT: TRect;
  BM: Vcl.Graphics.TBitmap;
begin
  Result := nil;

// GetDC에서 CreateDC로 변경
  DC := CreateDC('Display', nil, nil, nil);
//  DC := GetDC(hWindow);
  if DC <> 0 then
  begin
    ZeroMemory(@RT, SizeOf(TRect));
    if GetClientRect(hWindow, RT) then
    begin
      try
        Guard(BM, Vcl.Graphics.TBitmap.Create);
        BM.Width := RT.Width;
        BM.Height := RT.Height;

        if BitBlt(BM.Canvas.Handle,
                  0,
                  0,
                  RT.Width,
                  RT.Height,
                  DC,
                  RT.Left,
                  RT.Top,
                  SRCCOPY + CaptureBlt) then // CaptureBlt 추가, 윈도우 7 이하 투명 효과된 윈도우 캡쳐지원
        begin
          Result := TJPEGImage.Create;
          Result.Assign(BM);
        end;
      except
        Result := nil;
        exit;
      end;
    end;
    DeleteDC(DC);
//    ReleaseDC(0, DC);
  end;
end;

end.
