www.pudn.com > TMIDIPlayer_w_MidiSheet.rar > ShowMIDILyrics.pas, change:2014-02-08,size:78270b


// Unit ShowMIDILyrics 
// 
// This unit takes charge of displaying synchronized lyrics for kMIDIPlayer. 
// 
//       written by Silhwan Hyun  (hyunsh@hanafos.com) 
// 
// Contibutors 
//   Emil Weiss : Test & Advice for update/debug 
// 
// 
// Ver 1.1                     02 Jun 2012 
//   - Put a reasonable space between characters. 
//   - Fixed artifact bugs. 
// 
// Ver 1.0                     22 Mar 2012 
//   - Initial release 
// 
 
unit ShowMIDILyrics; 
 
// {$I config.inc} 
 
{$DEFINE MULTI_LANGUAGE} 
 
interface 
 
uses 
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, ActiveX, 
  Dialogs, ExtCtrls, Contnrs, 
  StdCtrls, Buttons, {$IFDEF MULTI_LANGUAGE}gnugettext,{$ENDIF} ReadMIDILyrics, GDIPAPI, GDIPOBJ, Menus, MIDIFile2, 
  AARotate_Fast, AdvImage; 
 
const 
  WM_SyncLyricsConfig  = WM_USER + 145; 
  SyncLyrics_Shown     = 1; 
  SyncLyrics_Closed    = 2; 
  SyncLyrics_StayOn    = 3; 
  SyncLyrics_InfoChanged = 4; 
  SyncLyrics_BackTransparency = 5; 
  SyncLyrics_ScaleAdj  = 6; 
  SyncLyrics_LocAdj    = 7; 
 
  SyncLyricsMinScale = 50; 
  SyncLyricsMaxScale = 145;  // ** You may encounter error(window disappearing) if Scale is greater than 148. 
 
  MaxShiftTime = 20000; 
 
type 
  TPlayerMode = (plmStandby, plmReady, plmStopped, plmPlaying, plmPaused); 
 
  TShowLyricsForm = class(TForm) 
    Timer1: TTimer; 
    PopupMenu1: TPopupMenu; 
    menuAdjustLyrics: TMenuItem; 
    N1: TMenuItem; 
    menuClose: TMenuItem; 
    menuShowSyncSetForm: TMenuItem; 
    picShowMenu: TAdvImage; 
    procedure Timer1Timer(Sender: TObject); 
    procedure FormCreate(Sender: TObject); 
    procedure btnCloseClick(Sender: TObject); 
    procedure FormShow(Sender: TObject); 
    procedure FormClose(Sender: TObject; var Action: TCloseAction); 
    procedure FormDestroy(Sender: TObject); 
    procedure FormMouseDown(Sender: TObject; Button: TMouseButton; 
      Shift: TShiftState; X, Y: Integer); 
    procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, 
      Y: Integer); 
    procedure FormMouseUp(Sender: TObject; Button: TMouseButton; 
      Shift: TShiftState; X, Y: Integer); 
    procedure menuAdjustLyricsClick(Sender: TObject); 
    procedure menuShowSyncSetFormClick(Sender: TObject); 
    procedure menuCloseClick(Sender: TObject); 
  //  procedure menuMergeIntoVisWindowClick(Sender: TObject); 
  //  procedure ApplicationEvents1Message(var Msg: tagMSG; 
  //    var Handled: Boolean); 
 
    procedure picButtonMouseEnter(Sender: TObject); 
    procedure picButtonMouseLeave(Sender: TObject); 
    procedure picButtonMouseDown(Sender: TObject; 
                                 Button: TMouseButton; Shift: TShiftState; X, Y: Integer); 
    procedure picButtonMouseUp(Sender: TObject; 
                               Button: TMouseButton; Shift: TShiftState; X, Y: Integer); 
  private 
    { Private declarations } 
    m_bShown : boolean; 
 
    SongTitle : UnicodeString; 
    Channel_Stat : TPlayerMode; 
    Playing_Pos : DWORD; 
 
    FCurLine : integer; 
    FCurSyllable : integer; 
    FCurLineColor : TColor; 
    FCurTitleColor : TColor; 
 
    CURSOR_RESIZE  : HCURSOR; 
 
    procedure SetChannelStat(ChannelStat : TPlayerMode); 
    procedure SetPlayingPos(PlayingPos : DWORD); 
    procedure SetTitle(Title : string); 
    procedure ReloadLyrics(MIDILyrics: TRawLyrics); 
    procedure ReloadBtnImage(Btn_No : integer; b_Pressed, b_Highlighted : boolean); 
    procedure SetImageBtn(img: TAdvImage); 
 
    procedure RelocateBtns(var BaseImage : TBitmap); 
 
  protected 
    procedure DrawButtons(var Dest : TBitmap); 
    procedure PaintBtnImage(var Dest : TBitmap; Btn_No : integer; LocX, LocY : integer; 
                            LastPressed, LastHighlighted : integer); 
     
  public 
    { Public declarations } 
    ScaleFactor : double; 
    TitleImage  : TBitmap; 
    BackImage2 : TBitmap; 
 
    procedure WndProc(var Message:TMessage); override; 
    procedure ClearLine(LineNum : word); 
    procedure DrawLine(LineStr : UnicodeString; var FontFamily : TGPFontFamily; 
                       LineColor : TColor; LineNum : word); 
    procedure DrawTitle(Title: string); 
    procedure DrawInactiveLine(LyricsLineNum : integer); 
    procedure DrawLyrics(ForceProcess : boolean); 
  //  procedure EMBEDWidthChange; 
    procedure RefreshWindow; 
    procedure RefreshWindow2; 
    procedure PrepareImageBtns; 
    procedure InitializeBackground(BackColor : TColor); 
    procedure MakeWndTransparent(Value : Boolean); 
    procedure RepaintLyrics; 
  //  procedure SetLyricsStayOnVisWnd(StayOnVisWnd : boolean); 
  end; 
 
// MyUpdateLayeredWindow is for old Delphi compiler. 
// You can use 'UpdateLayeredWindow' directly if you use Delphi 7 or newer version. 
  function MyUpdateLayeredWindow(Handle: THandle; hdcDest: HDC; pptDst: PPoint; 
                                 _psize: PSize; hdcSrc: HDC; pptSrc: PPoint; 
                                 crKey: COLORREF; pblend: PBLENDFUNCTION; 
                                 dwFlags: DWORD): Boolean; stdcall; 
                                 external 'User32.dll' name 'UpdateLayeredWindow'; 
 
  function GetAdvanceShiftLimit : integer; 
  function SyncLyricsScale : integer; 
 
  procedure SetMIDITimeOffset(Offset_DS : integer); 
  procedure GetMIDITimeOffset(var Offset_DS : integer); 
 
  function CreateRoundRectangle(rectangle: TGPRect; radius: integer): TGPGraphicsPath; 
 
 
var 
  ShowLyricsForm: TShowLyricsForm; 
 
implementation 
 
uses MIDITest, SetSyncTime, SyncLyricsAdjust; 
 
{$R *.dfm} 
 
 
const 
   AC_SRC_ALPHA = 1; 
   ULW_ALPHA = 2; 
   WS_EX_LAYERED = $80000; 
 
   TitleColorA = $0090A0FF;   // Title color for playing state 
   TitleColorB = $004060C0;   // Title color for non-playing state 
   Default_Transparency = 153;  // (255 - 153) / 255 => approx. 40 % 
   BackgroundColor_S = 0;  // Black   (for stay on Vis & Lyrics Window) 
 //  BackgroundColor_F = TColor($00FFBF9F); 
   BackgroundColor_F = TColor($007F7F7F); 
 
   MaxCharactersPerLine = 64; // Maximum characters per line 
   LyricsColorA = $008FBF8F; // lyrics color at non-playing state or next line to be played 
   LyricsColorC = clYellow;  // lyrics color for played or being played syllable 
   LyricsColorD = $00ffbfbf; // $008F6F5F; // lyrics color for to be played syllabel 
 
   TitleFont_1 = '나눔고딕 ExtraBold';  // for Korean language (가변폭 나눔고딕 굵은체 폰트) 
   LyricsFont_1 = '나눔고딕 ExtraBold'; // for Korean language (가변폭 나눔고딕 굵은체  폰트) 
  // TitleFont_1 = 'Tahoma'; 
  // LyricsFont_1 = 'Tahoma'; 
   TitleFont_2 = 'Tahoma';    // for English language 
   LyricsFont_2 = 'Tahoma';   // for English language 
   TitleFont_3 = '굴림';      // for Korean language (가변폭 바른돋움 폰트가 설치되어 있지 않을 경우 대체용) 
   LyricsFont_3 = '굴림';     // for Korean language (가변폭 바른돋움 폰트가 설치되어 있지 않을 경우 대체용) 
 
   TextHeight_B = 32;  // For big size text 
   TextHeight_M = 28;  // For medium size text 
   TextHeight_S = 24;  // For small size text 
 
   BorderMargin = 7; 
   TopOffset = 5; 
   GapBetweenLine = 16; 
   LineHeight = TextHeight_B + GapBetweenLine; 
 
   ImageBaseWidth = 800; 
 //  ImageBaseHeight = LineHeight * 2 + BorderMargin; 
   MinScaleFactor = SyncLyricsMinScale / 100; 
   MaxScaleFactor = SyncLyricsMaxScale / 100; 
 
   msgCount = 9; 
 
type 
  PARGBArray = ^TARGBArray; 
  TARGBArray = array[0..0] of TRGBQUAD; 
 
  TSeparateWinAttr = record 
    ItemValid  : boolean; 
    ScaleF     : double; 
    WinLocX    : integer; 
    WinLocY    : integer; 
  end; 
 
 { TCopyDataStructA = record 
    dwData : DWORD; 
    cbData : integer; 
    lpData : pAnsiChar; 
  end; } 
 
  {$IF CompilerVersion < 20}  // Delphi version is older than Delphi 2009 ? 
  DWORD_PTR = DWORD; 
  INT_PTR = INTEGER; 
  PDWORD_PTR = ^DWORD_PTR; 
  {$IFEND} 
 
var 
   StreamFormat : DWORD = 0; 
   SyncLyrics : TSyncLyrics; 
   ShiftTime : integer = 0;  // in ms 
 
   Last_ScaleFactor  : Double = 0; 
   ImageBaseHeight: integer; 
   TitleAtStandby: string; 
   TitleFont: string; 
   LyricsFont: string; 
   Gap_B: single;        // space between characters for big size text 
   Gap_M: single;        // space between characters for medium size text 
   Gap_S: single;        // space between characters for small size text 
   Bg_Transparency : integer;  // the transparency of Background image 
 
   UpdatedLyricsImage : boolean = false; 
   UpdatedTitleImage : boolean = false; 
 
   graphics  : TGPGraphics; 
   graphics2  : TGPGraphics; 
   GPPen_Sha : TGPPen; 
 //  Brush_Sha : TGPSolidBrush; 
 //  Brush_Trans : TGPSolidBrush; 
   FF_Norm   : TGPFontFamily; 
   FF_Title  : TGPFontFamily; 
   SF_Norm   : TGPStringFormat; 
   Path_Sha  : TGPGraphicsPath; 
 //  GPFont_Norm : TGPFont; 
   Path_Norm  : TGPGraphicsPath; 
   Path_Background : TGPGraphicsPath; 
   GPPen_Norm : TGPPen; 
   Brush_Norm : TGPSolidBrush; 
 
   Path_NormArray : array[1..MaxCharactersPerLine] of TGPGraphicsPath; 
 
   b_BtnPressed : boolean; 
   i_CursorOn : integer; 
 
 //  m_bStayOnVisWnd : boolean = true; 
   BackgroundColor : TCOlor; 
   IsSizing : boolean = false; 
   IsMoving : boolean = False; 
   Old_LocX, Old_LocY : integer; 
   MouseLeftSide : boolean; 
 
   SeparateWinAttr : TSeparateWinAttr; 
   GDIReady : boolean = true; 
 
   RawBackImage : TBitmap; 
   ElapsedImage : TBitmap; 
   Image_BtnArea : TBitmap; 
   Image_Controls : TBitmap; 
   ShowingBtns : boolean = false; 
   Last_Pressed : integer = 0; 
   Last_Highlighted : integer = 0; 
   LastCopiedXPos : integer; 
 
 // NoLimitByFirstTime : boolean; 
 
   // Define menu caption 
  { msgs : array[1..msgCount] of string 
          = ('Sync Adjust -0.2Sec',               //  1 
             'Sync Adjust +0.2Sec',               //  2 
             'Sync Adjust -2Sec',                 //  3 
             'Sync Adjust +2Sec',                 //  4 
             '&Show Sync Control Form',           //  5 
             '&Adjust Size && Transparency',      //  6 
             '&Merge Into Visualization',         //  7 
             'Close (&x)',                        //  8 
             'BASSPlay Sync Lyrics Window');      //  9  } 
 
 
function byterange(a : double) : byte; 
var 
   b : integer; 
begin 
   b := round(a); 
 
   if b < 0 then 
      b := 0; 
   if b > 255 then 
      b := 255; 
 
   result := b; 
end; 
 
// This function is used to load button image from Resource or PNG file which has alpha 
//  channel and returns the handle to bitmap. 
// The rgbReserved element of bitmap gets the data of alpha channel. 
// note) BackColor specifies the background color. This parameter is ignored if the bitmap 
//       is totally opaque. 
function LoadSourceImage(ImgFile : WideString; BackColor : TColor) : HBITMAP; 
var 
   image: GPIMAGE; 
   hbmReturn: HBITMAP; 
   format : integer; 
 
   hResource : HRSRC; 
   imageSize : DWORD; 
   hBuffer : HGLOBAL; 
   h_Global : HGLOBAL; 
   pData : pointer; 
   pBuffer : pointer; 
   hr : HRESULT; 
   Stream : IStream; 
 
begin 
   Result := 0; 
 
   if not GDIReady then 
      exit; 
 
   image := nil; 
 
 // Try to load image from Resouce 
   hResource := FindResource(0, 'BTNS_ADJUST', 'PNG'); 
   if (hResource <> 0) then 
   begin 
      imageSize := SizeofResource(0, hResource); 
      h_Global := LoadResource(0, hResource); 
      pData := LockResource(h_Global); 
 
      hBuffer := GlobalAlloc(GMEM_MOVEABLE, imageSize); 
      pBuffer := GlobalLock(hBuffer); 
 
      CopyMemory(pBuffer, pData, imageSize); 
      GlobalUnlock(hBuffer); 
 
      hr := CreateStreamOnHGlobal(hBuffer, TRUE, Stream); 
      if hr = S_OK then 
         GdipLoadImageFromStream(Stream, image); 
 
      GlobalFree(hBuffer); 
 
     { if image <> nil then 
      begin 
        GdipGetImagePixelFormat(image, format); 
        if format = PixelFormat32bppARGB then   // Accept iamges with alpha channel 
        begin 
          GdipCreateHBITMAPFromBitmap(pointer(image), hbmReturn, BackColor); 
          if hbmReturn <> 0 then 
             Result := hbmReturn; 
        end; 
      end; } 
   end; 
 
   if image = nil then    // Failed to load image from Resource ? 
   begin 
     if not FileExists(ImgFile) then 
        exit; 
 
   // Try to load image from file. 
     GdipLoadImageFromFile(PWideChar(ImgFile), image); 
   end; 
 
   if image <> nil then 
   begin 
     GdipGetImagePixelFormat(image, format); 
     if format = PixelFormat32bppARGB then   // Accept files with alpha channel 
     begin 
       GdipCreateHBITMAPFromBitmap(pointer(image), hbmReturn, BackColor); 
       if hbmReturn <> 0 then 
          Result := hbmReturn; 
     end; 
 
     GdipDisposeImage(image); 
   end; 
end; 
 
// This procedure combines two bitmap images. 
// If Transparent option is true then the image to be laid(=OverlayImg) on the 
// background image(=BackImg) is overlapped according to the value of "rgbReserved" 
// of each pixel. 
procedure CombineImage2(BackImg, OverlayImg : HBITMAP; LocX, LocY : integer; 
                        SrcR : TRect; Transparent : boolean); 
var 
   srcbmp, dstbmp : Bitmap; 
   srcdib : array of TRGBQUAD; 
   dstdib : array of TRGBQUAD; 
   srcdibbmap, dstdibbmap : TBITMAPINFO; 
   ldc : HDC; 
   DstIndex, SrcIndex : integer; 
   SrcX1, SrcY1, SrcX2, SrcY2 : integer; 
   sx, sy, dx, dy : integer; 
   i, j : integer; 
   Alpha : integer; 
 
begin 
   //Load the OverlayImg bitmaps information 
   if (GetObject(OverlayImg, sizeof(srcbmp), @srcbmp) = 0) then 
      exit; 
 
   if srcbmp.bmBitsPixel <> 32 then  //Transparent option is effective only for 32bit bitmap 
      Transparent := false; 
 
   if (srcbmp.bmBitsPixel <> 24) and (srcbmp.bmBitsPixel <> 32) then 
      exit; 
 
   //Load the BackImg bitmaps information 
   if (GetObject(BackImg, sizeof(dstbmp), @dstbmp) = 0) then 
      exit; 
 
   if LocX >= dstbmp.bmWidth then 
      exit; 
   if LocY >= dstbmp.bmHeight then 
      exit; 
   if (LocX + dstbmp.bmWidth) <= 0 then   // LocX can be < 0 
      exit; 
   if (LocY + dstbmp.bmHeight) <= 0 then  // LocY can be < 0 
      exit; 
 
   SrcX1 := SrcR.Left; 
   SrcY1 := SrcR.Top; 
   SrcX2 := SrcR.Right; 
   SrcY2 := SrcR.Bottom; 
   if (SrcY1 >= srcbmp.bmHeight) then 
      exit; 
   if (SrcX1 >= srcbmp.bmWidth) then 
      exit; 
 
   if (SrcY1 > SrcY2) or (SrcX1 > SrcX2) then 
      exit; 
 
   if SrcY1 < 0 then 
   begin 
      LocY := LocY + abs(SrcY1); 
      SrcY1 := 0; 
   end; 
   if LocY < 0 then 
   begin 
      SrcY1 := SrcY1 + abs(LocY); 
      LocY := 0; 
   end; 
   if SrcX1 < 0 then 
   begin 
      LocX := LocX + abs(SrcX1); 
      SrcX1 := 0; 
   end; 
   if LocX < 0 then 
   begin 
      SrcX1 := SrcX1 + abs(LocX); 
      LocX := 0; 
   end; 
   if SrcY2 >= srcbmp.bmHeight then 
      SrcY2 := srcbmp.bmHeight - 1; 
   if SrcX2 >= srcbmp.bmWidth then 
      SrcX2 := srcbmp.bmWidth - 1; 
 
  //Create the source dib array and the destdib array 
   SetLength(srcdib, srcbmp.bmWidth * srcbmp.bmHeight); 
   SetLength(dstdib, dstbmp.bmWidth * dstbmp.bmHeight); 
 
  //Load source bits into srcdib 
   srcdibbmap.bmiHeader.biSize := sizeof(srcdibbmap.bmiHeader); 
   srcdibbmap.bmiHeader.biWidth := srcbmp.bmWidth; 
   srcdibbmap.bmiHeader.biHeight := -srcbmp.bmHeight; 
   srcdibbmap.bmiHeader.biPlanes := 1; 
   srcdibbmap.bmiHeader.biBitCount := 32; 
   srcdibbmap.bmiHeader.biCompression := BI_RGB; 
 
   ldc := CreateCompatibleDC(0); 
   GetDIBits(ldc, OverlayImg, 0, srcbmp.bmHeight, srcdib, srcdibbmap, DIB_RGB_COLORS); 
   DeleteDC(ldc); 
 
   //Load dest bits into dstdib 
   dstdibbmap.bmiHeader.biSize := sizeof(dstdibbmap.bmiHeader); 
   dstdibbmap.bmiHeader.biWidth := dstbmp.bmWidth; 
   dstdibbmap.bmiHeader.biHeight := -dstbmp.bmHeight; 
   dstdibbmap.bmiHeader.biPlanes := 1; 
   dstdibbmap.bmiHeader.biBitCount := 32; 
   dstdibbmap.bmiHeader.biCompression := BI_RGB; 
 
   ldc := CreateCompatibleDC(0); 
   GetDIBits(ldc, BackImg, 0, dstbmp.bmHeight, dstdib, dstdibbmap, DIB_RGB_COLORS); 
   DeleteDC(ldc); 
 
   for j := LocY to LocY + (SrcY2 - SrcY1) do 
   begin 
     sy := (j - LocY) + SrcY1; 
     dy := j; 
     if dy > (dstbmp.bmHeight - 1) then 
        break; 
 
     for i := LocX to LocX + (SrcX2 - SrcX1) do 
     begin 
       sx := (i - LocX) + SrcX1; 
       dx := i; 
       if dx > (dstbmp.bmWidth - 1) then 
          break; 
 
       SrcIndex := sx + srcbmp.bmWidth * sy; 
       DstIndex := dx + dstbmp.bmWidth * dy; 
 
       if Transparent then 
       begin 
         Alpha := srcdib[SrcIndex].rgbReserved; 
        { if Alpha = 0 then   // 100% BackImg 
         begin 
            // Nothing to do 
         end 
         else} if Alpha = 255 then  // 100% OverlayImg 
         begin 
            begin 
              dstdib[DstIndex].rgbRed := srcdib[SrcIndex].rgbRed; 
              dstdib[DstIndex].rgbBlue := srcdib[SrcIndex].rgbBlue; 
              dstdib[DstIndex].rgbGreen := srcdib[SrcIndex].rgbGreen; 
            end; 
         end 
         else if (Alpha > 0) then 
         begin         // Mix the BackImg and OverlayImg 
           dstdib[DstIndex].rgbRed := byterange(srcdib[SrcIndex].rgbRed * Alpha / 255 
                                              + dstdib[DstIndex].rgbRed * (255 - Alpha) / 255); 
           dstdib[DstIndex].rgbBlue := byterange(srcdib[SrcIndex].rgbBlue * Alpha / 255 
                                              + dstdib[DstIndex].rgbBlue * (255 - Alpha) / 255); 
           dstdib[DstIndex].rgbGreen := byterange(srcdib[SrcIndex].rgbGreen * Alpha / 255 
                                              + dstdib[DstIndex].rgbGreen * (255 - Alpha) / 255); 
         end; 
 
      // Calculate the overall transparency. 
         dstdib[DstIndex].rgbReserved := byterange(255 - ((255 - dstdib[DstIndex].rgbReserved) * (255 - srcdib[SrcIndex].rgbReserved)) / 255); 
       end else 
       begin  // for (Transparent = false) 
         dstdib[DstIndex].rgbRed := srcdib[SrcIndex].rgbRed; 
         dstdib[DstIndex].rgbBlue := srcdib[SrcIndex].rgbBlue; 
         dstdib[DstIndex].rgbGreen := srcdib[SrcIndex].rgbGreen; 
       end; 
     end; // for i 
   end; // for j 
 
   SetDIBits(0, BackImg, 0, dstbmp.bmHeight, dstdib, dstdibbmap, DIB_RGB_COLORS); 
   SetLength(srcdib, 0); 
   SetLength(dstdib, 0); 
end; 
 
// GDI+ does not have the function to draw Round Rectangle. 
// So, we should prepare a own function for that. 
function CreateRoundRectangle(rectangle: TGPRect; radius: integer): TGPGraphicsPath; 
var 
  path : TGPGraphicsPath; 
  l, t, w, h, d : integer; 
begin 
  path := TGPGraphicsPath.Create; 
  l := rectangle.X; 
  t := rectangle.y; 
  w := rectangle.Width; 
  h := rectangle.Height; 
  d := radius shl 1; // divide by 2 
 
  // the lines beween the arcs are automatically added by the path 
  path.AddArc(l, t, d, d, 180, 90); // top left 
  path.AddArc(l + w - d, t, d, d, 270, 90); // top right 
  path.AddArc(l + w - d, t + h - d, d, d, 0, 90); // bottom right 
  path.AddArc(l, t + h - d, d, d, 90, 90); // bottom left 
  path.CloseFigure(); 
  result := path; 
end; 
 
// function MeasureString is prepared to substitue the TGPGraphics's MeasureString function. 
// Because TGPGraphics's MeasureString function reports incorrect result if we call it continuously 
// (A lyrics line may have several syllables. When we calculate the position of each syllable, this 
//  causes problem.) 
function MeasureString(G : TGPGraphics; F : TGPFont; S : UnicodeString; Count : integer) : TGPRectF; 
var 
   i, len : integer; 
   Str : UnicodeString; 
   RectF : TGPRectF; 
   rt : TGPRectF; 
   cr : TCharacterRange; 
   sf : TGPStringFormat; 
   rgn : TGPRegion; 
begin 
 // The preceding and trailing space characters are ignored at MeasureCharacterRanges. 
 // So, replace the preceding and trailing spaces to 'r'. 
   Str := S; 
   len := length(S); 
   if len = 0 then 
   begin 
     Result.Width := 0; 
     exit; 
   end; 
 
   if Str[1] = ' ' then 
   begin 
      i := 1; 
      repeat 
         if Str[i] = ' ' then 
            Str[i] := 'r' 
         else 
            break; 
         inc(i); 
      until i > len; 
   end; 
   if Str[len] = ' ' then 
   begin 
      i := len; 
      repeat 
         if Str[i] = ' ' then 
            Str[i] := 'r' 
         else 
            break; 
         dec(i); 
      until i = 0; 
   end; 
 
   RectF := MakeRect(0.0, 0.0, 65536.0, 65536.0); 
   if Count = - 1 then 
      cr := MakeCharacterRange(0, length(Str)) 
   else 
      cr := MakeCharacterRange(0, Count); 
 
   sf := TGPStringFormat.Create; 
   sf.SetMeasurableCharacterRanges(1, @cr); 
   rgn := TGPRegion.Create; 
   G.MeasureCharacterRanges(Str, Count, F, RectF, sf, 1, rgn); 
   rgn.GetBounds(rt, G); 
 
   rgn.Free; 
   sf.Free; 
   result := rt; 
end; 
 
function GetDisplayWidth(S: UnicodeString; SpaceWidth, GapWidth: single; G: TGPGraphics; GPFont: TGPFont): single; 
var 
  I: integer; 
  RectF: TGPRectF; 
begin 
  Result := 0; 
 
  if S = '' then 
    exit; 
 
  for I := 1 to Length(S) do 
  begin 
    if S[I] = ' ' then 
    begin 
      Result := Result + SpaceWidth; 
      Continue; 
    end; 
 
    RectF := MeasureString(G, GPFont, S[I], -1); 
   { if I = Length(S) then 
      Result := Result + RectF.Width 
    else } 
      Result := Result + RectF.Width + GapWidth; 
   end; 
end; 
 
procedure PrepareGDIPElements; 
var 
   i : integer; 
   Rect1 : TGPRect; 
begin 
   graphics := TGPGraphics.Create(ShowLyricsForm.TitleImage.Canvas.Handle); 
   graphics.SetPageUnit(UnitPixel); 
   graphics.SetSmoothingMode(SmoothingModeAntialias); 
   graphics.SetTextRenderingHint(TextRenderingHintAntiAlias); 
 
 // graphics2 : used to draw played part of lyrics on ElapsedImage. 
   graphics2 := TGPGraphics.Create(ElapsedImage.Canvas.Handle); 
   graphics2.SetPageUnit(UnitPixel); 
   graphics2.SetSmoothingMode(SmoothingModeAntialias); 
   graphics2.SetTextRenderingHint(TextRenderingHintAntiAlias); 
 
   FF_Norm  := TGPFontFamily.Create(LyricsFont); 
   FF_Title  := TGPFontFamily.Create(TitleFont); 
 
 //  GPFont_Norm := TGPFont.Create(FF_Norm, TextHeight, FontStyleBold, UnitPixel); 
   GPPen_Norm := TGPPen.Create(MakeColor(255, 10, 10, 10), 1.5); 
   Path_Norm  := TGPGraphicsPath.Create; 
   for i := 1 to MaxCharactersPerLine do 
      Path_NormArray[i] := TGPGraphicsPath.Create; 
   Brush_Norm := TGPSolidBrush.Create(MakeColor(255, 255, 255, 0)); 
 
   SF_Norm := TGPStringFormat.Create(0, 0); 
   SF_Norm.SetAlignment(StringAlignmentNear); 
   SF_Norm.SetFormatFlags(StringFormatFlagsNoWrap); 
 
   GPPen_Sha  := TGPPen.Create(MakeColor(200, 10, 10, 10), 4.0); 
  // Brush_Sha  := TGPSolidBrush.Create(MakeColor(200, 10, 10, 10)); 
   Path_Sha   := TGPGraphicsPath.Create; 
   Rect1 := MakeRect(0, 0, ImageBaseWidth - 1, ImageBaseHeight - 1); 
   Path_Background := CreateRoundRectangle(Rect1, 8); 
 
 //  Brush_Trans := TGPSolidBrush.Create(MakeColor(255, 0, 0, 0));  // Transparent brush 
end; 
 
procedure ReleaseGDIPElements; 
var 
   i : integer; 
begin 
   FF_Norm.Free; 
   FF_Title.Free; 
 //  GPFont_Norm.Free; 
   GPPen_Norm.Free; 
   Path_Norm.Free; 
   for i := 1 to MaxCharactersPerLine do 
      Path_NormArray[i].Free; 
   Brush_Norm.Free; 
   SF_Norm.Free; 
 
   GPPen_Sha.Free; 
  // Brush_Sha.Free; 
   Path_Sha.Free; 
   Path_Background.Free; 
 
 //  Brush_Trans.Free; 
   graphics.Free; 
   graphics2.Free; 
end; 
 
procedure SetMIDITimeOffset(Offset_DS : integer); 
begin 
   ShiftTime := Offset_DS;  // in ms 
end; 
 
procedure GetMIDITimeOffset(var Offset_DS : integer); 
begin 
   Offset_DS := ShiftTime; 
end; 
 
function GetAdvanceShiftLimit : integer; 
begin 
   if SyncLyrics.NumLine > 1 then 
      result := SyncLyrics.Lines[0].Syllables[0].StartTime 
   else 
      result := 20000; 
end; 
 
function SyncLyricsScale : integer; 
begin 
   result := round(ShowLyricsForm.ScaleFactor * 100); 
end; 
 
 
procedure TShowLyricsForm.RepaintLyrics; 
begin 
   if Channel_Stat = plmStandby then 
   begin 
     { if m_bStayOnVisWnd then 
         ClearLine(1) 
      else } 
         DrawLine(TitleAtStandby, FF_Title, TitleColorB, 1); 
      ClearLine(2); 
      RefreshWindow; 
   end 
   else if (Channel_Stat = plmReady) or (Channel_Stat = plmStopped) then 
   begin 
      DrawTitle(SongTitle); 
      if SyncLyrics.NumLine > 1 then 
         DrawInactiveLine(1); 
      RefreshWindow; 
   end else 
   begin   // for (Channel_Stat = plmPaused) or (Channel_Stat = plmPlaying) 
      if (SyncLyrics.NumLine = 0) or (FCurLine = 0) then 
          DrawTitle(SongTitle); 
      if (SyncLyrics.NumLine > 0) then 
          DrawLyrics(true); 
 
      RefreshWindow; 
   end; 
end; 
 
{ 
procedure TShowLyricsForm.SetLyricsStayOnVisWnd(StayOnVisWnd : boolean); 
begin 
   if StayOnVisWnd <> m_bStayOnVisWnd then 
   begin 
      if (not m_bStayOnVisWnd) and m_bShown then 
      begin 
       // Save Window Location & Size Factor to use at reverting to separate mode. 
         SeparateWinAttr.ScaleF := ScaleFactor; 
         SeparateWinAttr.WinLocX := Self.Left; 
         SeparateWinAttr.WinLocY := Self.Top; 
         SeparateWinAttr.ItemValid := true; 
      //   picClose2.Visible := true; 
      end; 
 
      m_bStayOnVisWnd := StayOnVisWnd; 
      if m_bStayOnVisWnd then 
      begin                      // Goes to merged mode 
         BackgroundColor := BackgroundColor_S; 
         InitializeBackground(BackgroundColor); 
         picClose2.Visible := false; 
         MakeWndTransparent(True); 
      end else 
      begin                      // Goes to separate mode 
         BackgroundColor := BackgroundColor_F; 
         InitializeBackground(BackgroundColor); 
         MakeWndTransparent(False); 
 
      // Restore Window Location & Size Factor with the values at separate mode. 
         if SeparateWinAttr.ItemValid then 
         begin 
            ScaleFactor := SeparateWinAttr.ScaleF; 
            Self.Left := SeparateWinAttr.WinLocX; 
            Self.Top := SeparateWinAttr.WinLocY; 
         end; 
         picClose2.Visible := true; 
      end; 
 
      RepaintLyrics; 
 
      PostMessage(MainForm.Handle, WM_SyncLyricsConfig, SyncLyrics_StayOn, integer(m_bStayOnVisWnd)); 
      PostMessage(Vis_LyricsDrawer.Handle, WM_SyncLyricsConfig, SyncLyrics_StayOn, integer(m_bStayOnVisWnd)); 
   end; 
end;  } 
 
 
procedure TShowLyricsForm.WndProc(var Message:TMessage); 
var 
   tmpScaleFactor : double; 
  // pPlayerStatRec : ^TPlayerStatRec; 
  // PlayerStatRec : TPlayerStatRec; 
   pTitle : pWideChar; 
begin 
   if (Message.Msg = WM_QueryEndSession) then 
   begin 
      Timer1.Enabled := false; 
      Message.Result := 1;    // Allow system termination 
   end else 
  { if Message.Msg = WM_VisNLyricsDrawerShown then 
   begin 
      if Message.WParam = 0 then  // Closed VisNLyricsDrawer 
         menuMergeIntoVisWindow.Enabled := false 
      else if (not m_bStayOnVisWnd) then 
         menuMergeIntoVisWindow.Enabled := true; 
   end else } 
   if Message.Msg = WM_SyncLyricsConfig then 
   begin 
     if Message.WParam = SyncLyrics_BackTransparency then 
     begin 
        if (Message.LParam < 0) or (Message.LParam > 255) then 
           exit; 
        BG_Transparency := Message.LParam; 
        InitializeBackground(BackgroundColor); 
        RepaintLyrics; 
     end 
     else if Message.WParam = SyncLyrics_ScaleAdj then 
     begin 
       tmpScaleFactor := Message.LParam / 100; 
 
       if tmpScaleFactor < minScaleFactor then 
          tmpScaleFactor := minScaleFactor; 
       if tmpScaleFactor > maxScaleFactor then 
          tmpScaleFactor := maxScaleFactor; 
       if tmpScaleFactor = ScaleFactor then 
          exit; 
 
       ScaleFactor := tmpScaleFactor; 
       RefreshWindow; 
     end 
     else if Message.WParam = SyncLyrics_LocAdj then 
     begin 
        Self.Left := HiWord(Message.LParam); 
        Self.Top := LoWord(Message.LParam); 
     end; 
   end else 
   if Message.Msg = WM_InformPlayerStat then 
   begin 
      if Message.WParam = Set_Lyrics then 
      begin 
         ReloadLyrics(PSyncLyrics(Message.LParam)^); 
      end else 
      if Message.WParam = Set_ChannelStat then 
         SetChannelStat(TPlayerMode(Message.LParam)) 
      else if Message.WParam = Set_PlayingPos then 
         SetPlayingPos(DWORD(Message.LParam)) 
      else if Message.WParam = Set_Title then 
      begin 
       //  pPlayerStatRec := pointer(Message.LParam); 
       //  SetTitle(pPlayerStatRec^.Title); 
         pTitle := pointer(Message.LParam); 
         {$IFDEF UNICODE} 
         SetTitle(string(pTitle)); 
         {$ELSE} 
         SetTitle(Widestring(pTitle)); // ** Added at 2012-06-04 
         {$ENDIF} 
      end; 
   end else 
      inherited WndProc(Message); 
end; 
 
procedure TShowLyricsForm.SetChannelStat(ChannelStat : TPlayerMode); 
var 
   OldStat : TPlayerMode; 
begin 
   OldStat := Channel_Stat; 
   Channel_Stat := ChannelStat; 
 
   if Channel_Stat = plmStandby then 
   begin 
      Timer1.Enabled := false; 
      DrawLine(TitleAtStandby, FF_Title, TitleColorB, 1); 
      ClearLine(2); 
      SyncLyrics.NumLine := 0; 
   end else 
   if (OldStat = plmPlaying) and (Channel_Stat <> plmPlaying) then 
   begin 
      Timer1.Enabled := false; 
      if (Channel_Stat <> plmPaused) or (SyncLyrics.NumLine = 0) then 
         DrawTitle(SongTitle) 
      else begin 
         if FCurLine = 0 then 
            DrawTitle(SongTitle) 
         else 
            FCurLine := 0; 
         DrawLyrics(false); 
      end; 
   end else 
   begin 
      if (Channel_Stat = plmPlaying) and (SyncLyrics.NumLine > 0) then 
      begin 
         if FCurLine = 0 then 
            DrawTitle(SongTitle); 
         Timer1.Enabled := true; 
      end else 
        if (Channel_Stat = plmPlaying) then 
           DrawTitle(SongTitle); 
   end; 
 
   if (Channel_Stat = plmStopped) and (SyncLyrics.NumLine > 0) then 
   begin 
      if (Channel_Stat = plmStopped) then 
         FCurLine := 0; 
      DrawInactiveLine(1); 
   end; 
 
   RefreshWindow; 
end; 
 
procedure TShowLyricsForm.SetPlayingPos(PlayingPos : DWORD); 
begin 
   Playing_Pos := MainForm.MidiFile.Tick2TimePos(PlayingPos); 
end; 
 
procedure TShowLyricsForm.SetTitle(Title : string); 
begin 
   SongTitle := Title; 
   DrawTitle(SongTitle); 
   if not Timer1.Enabled then 
      RefreshWindow; 
end; 
 
procedure TShowLyricsForm.ReloadLyrics(MIDILyrics: TRawLyrics); 
var 
  I, J: integer; 
begin 
 // Try to load Sync Lyrics Frame. 
   SyncLyrics := ReadSyncLyrics(MainForm.MidiFile, MIDILyrics, true{DiscardTimeZeroLyrics}); 
 
   for I := 0 to (SyncLyrics.NumLine - 1) do 
     for J := 0 to (SyncLyrics.Lines[I].NumSyllable - 1) do 
     begin 
       if J = 0 then 
         SyncLyrics.Lines[I].Syllables[0].CharacterStartNo := 1 
       else 
         SyncLyrics.Lines[I].Syllables[J].CharacterStartNo := 
            SyncLyrics.Lines[I].Syllables[J-1].CharacterStartNo + length(SyncLyrics.Lines[I].Syllables[J-1].aSyllable); 
     end; 
 
   DrawTitle(SongTitle); 
   if SyncLyrics.NumLine > 1 then 
      DrawLyrics(false); 
 
   if not Timer1.Enabled then 
      RefreshWindow; 
end; 
 
procedure TShowLyricsForm.ClearLine(LineNum : word); 
var 
 //  R : TRect; 
 //  GPRectF1 : TGPRectF; 
 
 //  P, p2 : PARGBArray; 
 //  x, y : integer; 
   StartY, LastY : integer; 
begin 
   if LineNum < 1 then 
      exit; 
 
   StartY := LineHeight * (LineNum - 1) + BorderMargin; 
  { if LineNum = 1 then 
      LastY := LineHeight + BorderMargin - 1 
   else 
      LastY := StartY + LineHeight - GapBetweenLine + 8; } 
   LastY := StartY + LineHeight;   // ** Changed at 2012-05-30 
 
 //  R := Rect(0, StartY, TitleImage.Width, LastY + 1); 
 //  TitleImage.Canvas.CopyRect(R, RawBackImage.Canvas, R); 
   BitBlt(TitleImage.Canvas.Handle, 0, StartY, TitleImage.Width, LastY - StartY + 1, 
          RawBackImage.Canvas.Handle, 0, StartY, SRCCOPY); 
 
   BitBlt(ElapsedImage.Canvas.Handle, 0, StartY, TitleImage.Width, LastY - StartY + 1, 
          RawBackImage.Canvas.Handle, 0, StartY, SRCCOPY); 
 
  { for y := StartY to LastY do 
   begin 
     P := TitleImage.Scanline[y]; 
     P2 := RawBackImage.Scanline[y]; 
     for x := 0 to (TitleImage.Width - 1) do 
     begin 
        P[x].rgbRed := P2[x].rgbRed; 
        P[x].rgbGreen := P2[x].rgbGreen; 
        P[x].rgbBlue := P2[x].rgbBlue; 
        P[x].rgbReserved := P2[x].rgbReserved; 
     end; 
   end; } 
 
 // Clearing with following GDI+ function shows errorneous transparency 
  { GPRectF1 := MakeRect(BorderMargin - 1.0, StartY, TitleImage.Width - BorderMargin, LastY + 1.0); 
   graphics.FillRectangle(Brush_Trans, GPRectF1);  } 
end; 
 
 
function NumSpaces(LineStr: UnicodeString): integer; 
var 
  I, N: integer; 
begin 
  N := 0; 
  for I := 1 to Length(LineStr) do 
    if LineStr[I] = ' ' then 
      inc(N); 
 
  result := N; 
end; 
 
procedure TShowLyricsForm.DrawLine(LineStr : UnicodeString; var FontFamily : TGPFontFamily; 
                                   LineColor : TColor; LineNum : word); 
var 
   I, M, N: integer; 
   ExtraSpace: single; 
   GPFont: TGPFont; 
  { GPRectF1,} GPRectF2 : TGPRectF; 
   X_Offset : single; 
   GapWidth : single; 
   SpaceWidth : single; 
   TextHeight : integer; 
   GPPointF : TGPPointF; 
   GPRectF3, GPRectF4: TGPRectF; 
begin 
   ClearLine(LineNum); 
   N := Length(LineStr); 
   M := NumSpaces(LineStr); 
 // Calculate the size of rectangle region for drawing lyrics (if we use DrawString) 
  { GPRectF1 := MakeRect(BorderMargin - 1.0, LineHeight * (LineNum - 1) + BorderMargin, 
                        TitleImage.Width - BorderMargin - 1.0, TextHeight_B + 3.0); } 
 
 // try with big size text 
   TextHeight := TextHeight_B; 
   GapWidth := Gap_B; 
   GPFont     := TGPFont.Create(FontFamily, TextHeight, FontStyleBold, UnitPixel); 
   GPPointF := MakePoint(BorderMargin - 1.0, LineHeight * (LineNum - 1) + BorderMargin + TopOffset); 
   graphics.MeasureString(LineStr, -1, GPFont, GPPointF, SF_Norm, GPRectF2); 
 // Calculate extra space to put gap between characters. (No gap after space characters) 
   ExtraSpace := (N - M) * GapWidth; 
 
 // Check if the required width for text drawing exceeds the width of background image 
   if (GPRectF2.Width +  ExtraSpace) > (ImageBaseWidth - BorderMargin * 2) then 
   begin 
   // try with medium size text 
     TextHeight := TextHeight_M; 
     GapWidth := Gap_M; 
     GPFont.Free; 
     GPFont     := TGPFont.Create(FF_Norm, TextHeight, FontStyleBold, UnitPixel); 
     graphics.MeasureString(LineStr, -1, GPFont, GPPointF, SF_Norm, GPRectF2); 
     ExtraSpace := (N - M) * GapWidth; 
   end; 
   if (GPRectF2.Width + ExtraSpace) > (ImageBaseWidth - BorderMargin * 2) then 
   begin 
   // use small size text 
     TextHeight := TextHeight_S; 
     GapWidth := Gap_S; 
     GPFont.Free; 
     GPFont     := TGPFont.Create(FF_Norm, TextHeight, FontStyleBold, UnitPixel); 
     graphics.MeasureString(LineStr, -1, GPFont, GPPointF, SF_Norm, GPRectF2); 
     ExtraSpace := (N - M) * GapWidth; 
   end; 
 
   GPRectF2.Width := GPRectF2.Width + ExtraSpace; 
   X_Offset := (TitleImage.Width - BorderMargin * 2 - GPRectF2.Width) / 2; 
   GPRectF3 := MeasureString(graphics, GPFont, 'ABCD', -1); 
   GPRectF4 := MeasureString(graphics, GPFont, 'AB CD', -1); 
   SpaceWidth := GPRectF4.Width - GPRectF3.Width + 2 * (TextHeight / TextHeight_B); 
 
 // Set brush to fill the inner area of character. 
   Brush_Norm.SetColor(MakeColor(255, GetRValue(LineColor), GetGValue(LineColor), GetBValue(LineColor))); 
 
 // Use following 2 lines if you want to use DrawString instead of DrawPath 
 {  graphics.SetTextRenderingHint(TextRenderingHintAntiAlias); 
   graphics.DrawString(LineStr, -1, GPFont, MakePoint(X_Offset, GPRectF1.Y), Brush_Norm); } 
 
 // Use following lines if you want to use DrawPath instead of DrawString 
   GPRectF2.X := X_Offset; 
 
   for I := 1 to Length(LineStr) do 
   begin 
     Path_Norm.Reset; 
     Path_Sha.Reset; 
     if LineStr[I] = ' ' then 
     begin 
       GPRectF2.X := GPRectF2.X + SpaceWidth; 
       Continue; 
     end; 
 
     try 
       Path_Norm.AddString(LineStr[I], -1, FontFamily, 
                        FontStyleBold, TextHeight, GPRectF2, SF_Norm); 
       Path_Sha.AddString(LineStr[I], -1, FontFamily, 
                        FontStyleBold, TextHeight, GPRectF2, SF_Norm); 
       graphics.DrawPath(GPPen_Sha, Path_Sha); 
       graphics.DrawPath(GPPen_Norm, Path_Norm); 
       graphics.FillPath(Brush_Norm, Path_Norm); 
       GPRectF3 := MeasureString(graphics, GPFont, LineStr[I], -1); 
       GPRectF2.X := GPRectF2.X + GPRectF3.Width + GapWidth; 
 
     finally 
 
     end; 
 
   end; 
 
   GPFont.Free; 
 
end; 
 
procedure TShowLyricsForm.DrawTitle(Title: string); 
var 
   TitleColor : TColor; 
begin 
 // Display Title text 
   if (Channel_Stat = plmPlaying) then 
      TitleColor  := TitleColorA 
   else 
      TitleColor  := TitleColorB; 
   FCurTitleColor := TitleColor; 
  { if IsNetRadio and (StationName <> '') then 
      DrawLine(StationName, FF_Title, TitleColor, 1); 
   else } 
      DrawLine(SongTitle, FF_Title, TitleColor, 1); 
   if SyncLyrics.NumLine = 0 then 
      ClearLine(2); 
 
   UpdatedTitleImage := true; 
end; 
 
 
procedure TShowLyricsForm.DrawInactiveLine(LyricsLineNum : integer); 
var 
   LineStr : UnicodeString; 
   j : integer; 
   SeparatorPos : integer; 
begin 
   LineStr := ''; 
   for j := 1 to SyncLyrics.Lines[LyricsLineNum -1].NumSyllable do 
     LineStr := LineStr + SyncLyrics.Lines[LyricsLineNum - 1].Syllables[J - 1].aSyllable; 
 
   SeparatorPos := pos('/', LineStr); 
   if SeparatorPos > 0 then 
      LineStr := copy(LineStr, 1 , SeparatorPos -1); 
   DrawLine(LineStr, FF_Norm, LyricsColorA, 2); 
 
   UpdatedLyricsImage := true; 
end; 
 
procedure TShowLyricsForm.DrawLyrics(ForceProcess : boolean); 
var 
   CurLine : integer; 
   CurSyllable : integer; 
   TextHeight : integer; 
   CurLineColor : TColor; 
   LastWordNum : integer; 
 
   m, n, j, k : integer; 
   {$IFDEF UNICODE} 
   S: string; 
   {$ELSE} 
   S: UnicodeString; 
   {$ENDIF} 
   ArrayIndex: integer; 
   CharacterStartNo: DWORD; 
   ExtraSpace: single; 
   GapWidth : single; 
   SpaceWidth : single; 
 
   SeparatorPos : integer; 
   LineStr : UnicodeString; 
   SubLangStr : UnicodeString; 
 
 //  graphics : TGPGraphics; 
   GPFont   : TGPFont; 
   Brush_Norm2 : TGPSolidBrush; 
 
   GPRectF1, GPRectF2 : TGPRectF; 
   GPRectF3, GPRectF4 : TGPRectF; 
   DrawColor : TColor; 
 
   WidthTotal{, HeightMax} : single; 
   S_Left : single; 
   S_Right : integer; 
   CurLineChanged : boolean; 
   CurColorChanged : boolean; 
   CurSyllableChanged : boolean; 
 
   GPPointF : TGPPointF; 
 
 {function AllSpaces(s : string): boolean; 
   var 
     I : integer; 
   begin 
     result := true; 
 
     for I := 1 to Length(s) do 
       if s[I] <> ' ' then 
       begin 
         result := false; 
         break; 
       end; 
 
   end; } 
 
 // Gets the width to be copied according to the elapsed time. 
  function GetRightX(LineNo, Time_Pos: DWORD): single; 
  var 
    n : integer; 
    NextLineStart : DWORD; 
    SyllableNo : integer; 
    WordWidth : single; 
    NumSyllable : DWORD; 
    Adjusted : boolean; 
    Ratio : single; 
    NextSyllableTime, TimeInterval : DWORD; 
  begin 
    SyllableNo := 0; 
    result := 0; 
 
    if Time_Pos < (SyncLyrics.Lines[LineNo - 1].Syllables[0].StartTime + ShiftTime) then 
      exit; 
 
    NumSyllable := SyncLyrics.Lines[LineNo - 1].NumSyllable; 
    if LineNo < (SyncLyrics.NumLine - 1) then 
      NextLineStart := SyncLyrics.Lines[LineNo].Syllables[0].StartTime 
    else 
    // for the last lyrics line 
      if SyncLyrics.Lines[LineNo - 1].Syllables[NumSyllable - 1].Duration > 0 then 
        NextLineStart := SyncLyrics.Lines[LineNo - 1].Syllables[NumSyllable - 1].StartTime 
                       + SyncLyrics.Lines[LineNo - 1].Syllables[NumSyllable - 1].Duration 
      else 
      // Take 1 second if we do not know the Duration of last note. 
        NextLineStart := SyncLyrics.Lines[LineNo - 1].Syllables[NumSyllable - 1].StartTime + 1000; 
 
    if Time_Pos >= (NextLineStart + ShiftTime) then 
    begin 
      WordWidth := 0; 
      for n := 0 to (NumSyllable - 1) do 
        WordWidth := WordWidth + SyncLyrics.Lines[LineNo - 1].Syllables[n].WordWidth; 
      result := WordWidth; 
      exit; 
    end; 
 
    if Playing_Pos >= (SyncLyrics.Lines[LineNo - 1].Syllables[NumSyllable - 1].StartTime + ShiftTime) then 
       SyllableNo := NumSyllable 
    else for n := 2 to NumSyllable do 
      if Time_Pos < (SyncLyrics.Lines[LineNo - 1].Syllables[n - 1].StartTime + ShiftTime) then 
      begin 
        SyllableNo := (n - 1); 
        break; 
      end; 
 
    if SyllableNo = 0 then 
      exit; 
 
  // 연주 중인 음절 뒤에 같은 시각으로 나타나는 공백 문자의 영향을 피하도록 한다.  (공백문자가 
  //  차지한 문자폭은 계산에서 배제한다.) 
    if SyllableNo >= 2 then 
    // "문자" - "공백" 으로 음절이 이어진 경우 
      if SyncLyrics.Lines[LineNo - 1].Syllables[SyllableNo-1].StartTime 
        = SyncLyrics.Lines[LineNo - 1].Syllables[SyllableNo-2].StartTime then 
      begin 
        SyllableNo := SyllableNo - 1; 
        Adjusted := true; 
      end else 
        Adjusted := false 
    else 
      Adjusted := false; 
 
   // SyllableNo는 현재 연주 중인 음절의 다음 음절 번호이다. 즉, 현재 연주 중인 음절 번호는 
   // SyllableNo - 1 이다. 
    if SyllableNo = NumSyllable then 
      NextSyllableTime := NextLineStart 
    else 
      if Adjusted then 
        NextSyllableTime := SyncLyrics.Lines[LineNo - 1].Syllables[SyllableNo+1].StartTime 
      else 
        NextSyllableTime := SyncLyrics.Lines[LineNo - 1].Syllables[SyllableNo].StartTime; 
 
    TimeInterval := NextSyllableTime - SyncLyrics.Lines[LineNo - 1].Syllables[SyllableNo-1].StartTime; 
    WordWidth := 0; 
 
    for n := 0 to SyllableNo - 2 do 
      WordWidth := WordWidth + SyncLyrics.Lines[LineNo - 1].Syllables[n].WordWidth; 
 
  // 시간 경과에 비례하여 채색할 음절의 가로 채색 비율 값을 구하되 최소 채색 비율을 적용한다. 
    Ratio := (Time_Pos - (SyncLyrics.Lines[LineNo - 1].Syllables[SyllableNo - 1].StartTime + ShiftTime)) 
                       / TimeInterval; 
    if Ratio < 0.2 then 
      Ratio := 0.2; 
    result := WordWidth + 
               SyncLyrics.Lines[LineNo - 1].Syllables[SyllableNo - 1].WordWidth * Ratio; 
 
  end; 
 
begin 
   CurLine := 0; 
 
   if Channel_Stat <> plmStandby then 
     if (Channel_Stat <> plmStopped) and (Channel_Stat <> plmReady) then 
       if Playing_Pos >= (SyncLyrics.Lines[SyncLyrics.NumLine - 1].StartTime + ShiftTime) then 
         CurLine := SyncLyrics.NumLine 
       else if Playing_Pos >= (SyncLyrics.Lines[0].StartTime + ShiftTime) then 
         for n := 2 to SyncLyrics.NumLine do 
           if Playing_Pos < (SyncLyrics.Lines[n - 1].StartTime + ShiftTime) then 
           begin 
             CurLine := (n - 1); 
             break; 
          end; 
 
   if CurLine = 0 then 
   begin 
      if SyncLyrics.NumLine > 1 then 
         DrawInactiveLine(1); 
      FCurLine := 0; 
      exit; 
   end; 
 
 // Determine the color of current playing line 
   if (Channel_Stat = plmPlaying) then 
      CurLineColor := LyricsColorC 
   else 
      CurLineColor := LyricsColorD; 
 
 // Determine the syllable currenly being played 
   CurSyllable := 0; 
   if SyncLyrics.Lines[CurLine - 1].NumSyllable = 1 then 
      CurSyllable := 1 
   else begin 
     LastWordNum := SyncLyrics.Lines[CurLine - 1].NumSyllable; 
     if Playing_Pos >= (SyncLyrics.Lines[CurLine - 1].Syllables[LastWordNum - 1].StartTime + ShiftTime) then 
        CurSyllable := LastWordNum 
     else 
       for n := 2 to LastWordNum do 
         if Playing_Pos < (SyncLyrics.Lines[CurLine - 1].Syllables[n - 1].StartTime + ShiftTime) then 
         begin 
           CurSyllable := (n - 1); 
           break; 
         end; 
   end; 
 
   if CurSyllable = 0 then 
      exit; 
 
   if CurLine <> FCurLine then 
      CurLineChanged := true 
   else 
      CurLineChanged := false; 
   if CurLineColor <> FCurLineColor then 
      CurColorChanged := true 
   else 
      CurColorChanged := false; 
   if CurSyllable <> FCurSyllable then 
      CurSyllableChanged := true 
   else 
      CurSyllableChanged := false; 
 
 // Skip if it is not needed to be repainted. 
  { if not ForceProcess then 
      if (not CurLineChanged) and (not CurColorChanged) and (not CurSyllableChanged) then 
         exit; } 
 
   TextHeight := TextHeight_B; 
   GapWidth := Gap_B; 
   GPFont   := TGPFont.Create(FF_Norm, TextHeight, FontStyleBold, UnitPixel); 
   Brush_Norm2 := TGPSolidBrush.Create(MakeColor(255, 255, 255, 0)); 
 
 // The Path information of all syllables of a certain line of lyrics is obtained at 
 //  changing the the line to be played. 
 // Also drawing the border of text of a certain line of lyrics is performed at 
 //  changing the the line to be played. 
 // After that, only the inner path area is painted according to the change of current 
 //  syllable. 
   if CurLineChanged or ForceProcess then 
   begin 
     ClearLine(1); 
 
     LineStr := ''; 
     SubLangStr := ''; 
     LastCopiedXPos := 0; 
   //  n := Length(LineStr); 
   //  m := NumSpaces(LineStr); 
 
     if SyncLyrics.Lines[CurLine - 1].NumSyllable = 1 then 
     begin 
        LineStr := SyncLyrics.Lines[CurLine - 1].Syllables[0].aSyllable; 
        SeparatorPos := pos('/', LineStr); 
        if SeparatorPos > 0 then 
        begin 
           SubLangStr := copy(LineStr, SeparatorPos + 1, length(LineStr) - SeparatorPos); 
           LineStr := copy(LineStr, 1, SeparatorPos - 1); 
        end; 
     end else 
       for j := 1 to SyncLyrics.Lines[CurLine - 1].NumSyllable do 
         LineStr := LineStr + SyncLyrics.Lines[CurLine - 1].Syllables[J - 1].aSyllable; 
 
     n := Length(LineStr);      // ** Repositioned 
     m := NumSpaces(LineStr);   // ** Repositioned 
 
   // Measuring the size of rectangle region for drawing lyrics using GDI+ 
     GPRectF1 := MakeRect(BorderMargin - 1.0, BorderMargin + TopOffset, 
                         TitleImage.Width - BorderMargin - 1.0, TextHeight + 3); 
     WidthTotal := 0; 
   //  HeightMax := 0; 
 
     GPPointF := MakePoint(BorderMargin - 1.0, BorderMargin + TopOffset); 
     graphics.MeasureString(LineStr, -1, GPFont, GPPointF, SF_Norm, GPRectF2); 
   // Calculate extra space to put gap between characters. (No gap after space characters) 
     ExtraSpace := (n - m) * GapWidth; 
 
   // Check if the required width for text drawing exceeds the width of background image 
     if (GPRectF2.Width + ExtraSpace) > (ImageBaseWidth - BorderMargin * 2) then 
     begin 
     // try with medium size text 
       TextHeight := TextHeight_M; 
       GapWidth := Gap_M; 
       GPFont.Free; 
       GPFont     := TGPFont.Create(FF_Norm, TextHeight, FontStyleBold, UnitPixel); 
       graphics.MeasureString(LineStr, -1, GPFont, GPPointF, SF_Norm, GPRectF2); 
       ExtraSpace := (n - m) * GapWidth; 
     end; 
     if (GPRectF2.Width + ExtraSpace) > (ImageBaseWidth - BorderMargin * 2) then 
     begin 
     // use small size text 
       TextHeight := TextHeight_S; 
       GapWidth := Gap_S; 
       GPFont.Free; 
       GPFont     := TGPFont.Create(FF_Norm, TextHeight, FontStyleBold, UnitPixel); 
       graphics.MeasureString(LineStr, -1, GPFont, GPPointF, SF_Norm, GPRectF2); 
       ExtraSpace := (n - m) * GapWidth; 
     end; 
 
     GPRectF2.Width := GPRectF2.Width + ExtraSpace; 
     GPRectF3 := MeasureString(graphics, GPFont, 'ABCD', -1); 
     GPRectF4 := MeasureString(graphics, GPFont, 'AB CD', -1); 
     SpaceWidth := GPRectF4.Width - GPRectF3.Width + 2 * (TextHeight / TextHeight_B); 
 
    // We should add the width for each syllable at measuring with GDI+. 
    // ( The width for Space character is differently counted, in Syllable vs in a string) 
     if SyncLyrics.Lines[CurLine - 1].NumSyllable = 1 then 
     begin 
       { GPRectF2 := MeasureString(graphics, GPFont, LineStr, -1); 
        SyncLyrics.Lines[CurLine].Syllables[1].WordWidth := GPRectF2.Width; } 
        WidthTotal := GPRectF2.Width; 
       { if GPRectF2.Height > HeightMax then 
           HeightMax := GPRectF2.Height; } 
     end else 
       for j := 1 to SyncLyrics.Lines[CurLine - 1].NumSyllable do 
       begin 
      // graphics.MeasureString report incorrect result 
      { graphics.MeasureString(SyncLyrics.Lines[CurLine].Syllables[J].aSyllable, -1, 
                                GPFont, GPRectF1, GPRectF2); } 
         if SyncLyrics.Lines[CurLine - 1].Syllables[J - 1].aSyllable = '' then 
           SyncLyrics.Lines[CurLine - 1].Syllables[J - 1].WordWidth := 0 
         else begin 
          { GPRectF2 := MeasureString(graphics, GPFont, 
                                   SyncLyrics.Lines[CurLine - 1].Syllables[J - 1].aSyllable, -1); } 
           GPRectF2.Width := GetDisplayWidth(SyncLyrics.Lines[CurLine - 1].Syllables[J - 1].aSyllable, 
                             SpaceWidth, GapWidth, graphics, GPFont); 
           SyncLyrics.Lines[CurLine - 1].Syllables[J - 1].WordWidth := GPRectF2.Width; 
         end; 
         WidthTotal := WidthTotal + GPRectF2.Width; 
        { if GPRectF2.Height > HeightMax then 
            HeightMax := GPRectF2.Height; } 
       end; 
 
     SyncLyrics.Lines[CurLine - 1].LineWidth := WidthTotal; 
     S_Left := (TitleImage.Width - BorderMargin * 2 - WidthTotal) / 2; 
     LastCopiedXPos := round(S_Left); 
     GPRectF1.Y := BorderMargin * 1.0 + TopOffset; 
 
     if SyncLyrics.Lines[CurLine - 1].NumSyllable = 1 then 
     begin 
       DrawColor := CurLineColor; 
       Brush_Norm.SetColor(MakeColor(255, GetRValue(DrawColor), GetGValue(DrawColor), GetBValue(DrawColor))); 
 
     // Use the previously measuried width 
       GPRectF1.X := S_Left; 
 
     // Draw a syllable 
       for J := 1 to Length(LineStr) do 
       begin 
         Path_NormArray[J].Reset; 
         Path_Sha.Reset; 
         if LineStr[J] = ' ' then 
         begin 
           GPRectF1.X := GPRectF1.X + SpaceWidth; 
           Continue; 
         end; 
 
         Path_NormArray[J].AddString(LineStr[J], -1, FF_Norm, 
                        FontStyleBold, TextHeight, GPRectF1, SF_Norm); 
         Path_Sha.AddString(LineStr[J], -1, FF_Norm, 
                        FontStyleBold, TextHeight, GPRectF1, SF_Norm); 
         graphics.DrawPath(GPPen_Sha, Path_Sha); 
         graphics.DrawPath(GPPen_Norm, Path_NormArray[J]); 
         graphics.FillPath(Brush_Norm, Path_NormArray[J]); 
         GPRectF3 := MeasureString(graphics, GPFont, LineStr[J], -1); 
         GPRectF1.X := GPRectF1.X + GPRectF3.Width + GapWidth; 
         if J = MaxCharactersPerLine then 
           break; 
       end; 
 
     end else 
     for k := 1 to SyncLyrics.Lines[CurLine - 1].NumSyllable do 
     begin 
      { if (k < CurSyllable) then 
          DrawColor := CurLineColor   // 연주가 완료된 부분의 색상 
       else 
          DrawColor := LyricsColorD;  // 아직 연주가 완료되지 않은 부분의 색상 
     //  Brush_Norm.SetColor(MakeColor(255, GetRValue(DrawColor), GetGValue(DrawColor), GetBValue(DrawColor)));  } 
       Brush_Norm.SetColor(MakeColor(255, GetRValue(LyricsColorD), GetGValue(LyricsColorD), GetBValue(LyricsColorD))); 
     //  Brush_Norm2.SetColor(MakeColor(255, GetRValue(CurLineColor), GetGValue(CurLineColor), GetBValue(CurLineColor))); 
       Brush_Norm2.SetColor(MakeColor(255, GetRValue(LyricsColorC), GetGValue(LyricsColorC), GetBValue(LyricsColorC))); 
 
     // Use the previously measuried width 
       GPRectF1.X := S_Left; 
       S_Left := S_Left + SyncLyrics.Lines[CurLine - 1].Syllables[k - 1].WordWidth; 
 
     // Draw a syllable 
       S := SyncLyrics.Lines[CurLine - 1].Syllables[k - 1].aSyllable; 
       CharacterStartNo := SyncLyrics.Lines[CurLine - 1].Syllables[k - 1].CharacterStartNo; 
     // 한 글자 그린 후 글자간 여백을 띄우고 다음 글자를 그리는 식으로 가사를 쓴다. 
       for J := 1 to Length(S) do 
       begin 
         ArrayIndex := CharacterStartNo + J - 1; 
         Path_NormArray[ArrayIndex].Reset; 
         Path_Sha.Reset; 
         if S[J] = ' ' then 
         begin 
           GPRectF1.X := GPRectF1.X + SpaceWidth; 
           Continue; 
         end; 
 
         Path_NormArray[ArrayIndex].AddString(S[J], -1, FF_Norm, 
                        FontStyleBold, TextHeight, GPRectF1, SF_Norm); 
         Path_Sha.AddString(S[J], -1, FF_Norm, 
                        FontStyleBold, TextHeight, GPRectF1, SF_Norm); 
         graphics.DrawPath(GPPen_Sha, Path_Sha); 
         graphics.DrawPath(GPPen_Norm, Path_NormArray[ArrayIndex]); 
         graphics.FillPath(Brush_Norm, Path_NormArray[ArrayIndex]); 
 
       // graphics2는 연주가 완료된 상태의 이미지를 갖는다. 
         graphics2.DrawPath(GPPen_Sha, Path_Sha); 
         graphics2.DrawPath(GPPen_Norm, Path_NormArray[ArrayIndex]); 
         graphics2.FillPath(Brush_Norm2, Path_NormArray[ArrayIndex]); 
 
         GPRectF3 := MeasureString(graphics, GPFont, S[J], -1); 
         GPRectF1.X := GPRectF1.X + GPRectF3.Width + GapWidth; 
         if ArrayIndex = MaxCharactersPerLine then 
           break; 
       end; 
       if ArrayIndex = MaxCharactersPerLine then 
         break; 
     end; 
 
     if SubLangStr <> '' then 
        DrawLine(SubLangStr, FF_Norm, LyricsColorA, 2) 
     else if CurLine < SyncLyrics.NumLine then 
        DrawInactiveLine(CurLine + 1) 
     else 
        ClearLine(2); 
 
     FCurLine := CurLine; 
     FCurSyllable := CurSyllable; 
     FCurLineColor := CurLineColor; 
   end else if CurColorChanged then 
   begin 
     for k := 1 to SyncLyrics.Lines[CurLine - 1].NumSyllable do 
     begin 
       if SyncLyrics.Lines[CurLine - 1].NumSyllable = 1 then 
          DrawColor := CurLineColor 
       else if (k < CurSyllable) then 
          DrawColor := CurLineColor 
       else 
          DrawColor := LyricsColorD; 
     //  Brush_Norm.SetColor(MakeColor(255, GetRValue(DrawColor), GetGValue(DrawColor), GetBValue(DrawColor))); 
       Brush_Norm.SetColor(MakeColor(255, GetRValue(DrawColor), GetGValue(DrawColor), GetBValue(DrawColor))); 
       CharacterStartNo := SyncLyrics.Lines[CurLine - 1].Syllables[k - 1].CharacterStartNo; 
       for J := 1 to Length(SyncLyrics.Lines[CurLine - 1].Syllables[k - 1].aSyllable) do 
       begin 
       // Redraw a syllable  (Just to change the color of inner path) 
         ArrayIndex := CharacterStartNo + J - 1; 
         graphics.FillPath(Brush_Norm, Path_NormArray[ArrayIndex]); 
         if ArrayIndex = MaxCharactersPerLine then 
           break; 
       end; 
       if ArrayIndex = MaxCharactersPerLine then 
         break; 
     end; 
 
     FCurLineColor := CurLineColor; 
   end else if CurSyllableChanged then 
   begin 
     if CurSyllable > FCurSyllable then 
     begin 
     // (CurSyllable > FCurSyllable)인 경우는 아래의 이미지 복사 루틴에서 처리하도록 한다. 
      { DrawColor := CurLineColor; 
       Brush_Norm.SetColor(MakeColor(255, GetRValue(DrawColor), GetGValue(DrawColor), GetBValue(DrawColor))); 
       for j := (FCurSyllable + 1) to (CurSyllable - 1) do 
       // Redraw a syllable  (Just to change the color of inner path) 
         graphics.FillPath(Brush_Norm, Path_NormArray[j]); } 
     end else 
     begin 
       DrawColor := LyricsColorD; 
       Brush_Norm.SetColor(MakeColor(255, GetRValue(DrawColor), GetGValue(DrawColor), GetBValue(DrawColor))); 
       for k := CurSyllable to FCurSyllable do 
       begin 
       // Redraw a syllable  (Just to change the color of inner path) 
         CharacterStartNo := SyncLyrics.Lines[CurLine - 1].Syllables[k - 1].CharacterStartNo; 
         for J := 1 to Length(SyncLyrics.Lines[CurLine - 1].Syllables[k - 1].aSyllable) do 
         begin 
           ArrayIndex := CharacterStartNo + j - 1; 
           graphics.FillPath(Brush_Norm, Path_NormArray[ArrayIndex]); 
           if ArrayIndex = MaxCharactersPerLine then 
             break; 
         end; 
         if ArrayIndex = MaxCharactersPerLine then 
           break; 
       end; 
       LastCopiedXPos := round((TitleImage.Width - BorderMargin * 2 - SyncLyrics.Lines[CurLine - 1].LineWidth) / 2); 
     end; 
 
     FCurSyllable := CurSyllable; 
   end else 
     if SyncLyrics.Lines[CurLine - 1].NumSyllable > 1 then 
       if CurSyllable = FCurSyllable then 
       begin 
      // 연주 경과시간에 비례해서 연주완료 이미지(ElapsedImage)를 TitleImage에 복사한다. 
         S_Left := (TitleImage.Width - BorderMargin * 2 - SyncLyrics.Lines[CurLine - 1].LineWidth) / 2; 
         S_Right := round(S_Left + GetRightX(CurLine, Playing_Pos)); 
         BitBlt(TitleImage.Canvas.Handle, round(LastCopiedXPos), BorderMargin + TopOffset, S_Right - LastCopiedXPos, LineHeight, 
            ElapsedImage.Canvas.Handle, LastCopiedXPos, BorderMargin + TopOffset, SRCCOPY); 
         LastCopiedXPos := S_Right; 
       end; 
 
   GPFont.Free; 
   Brush_Norm2.Free; 
   UpdatedLyricsImage := true; 
end; 
 
procedure TShowLyricsForm.PaintBtnImage(var Dest : TBitmap; Btn_No : integer; LocX, LocY : integer; 
                                        LastPressed, LastHighlighted : integer); 
var 
   X1, X2 : integer; 
   Y1, Y2 : integer; 
begin 
 // Get the horizontal position(from X1, to X2) of the region where desired button image is drawn. 
   case Btn_No of 
      98 : begin   // Button "Show Control" 
              X1 := 102; X2 := 117; 
           end; 
      99 : begin   // Button "Close" 
              X1 := 68; X2 := 83; 
           end; 
      else 
         exit; 
   end; 
 
 // Get the vertical position(from Y1, to Y2) of the region where desired button image is drawn 
   if LastHighlighted = Btn_No then begin 
      Y1 := 17; Y2 := 32; 
   end else 
   if LastPressed = Btn_No then begin 
      Y1 := 34; Y2 := 49 
   end else begin 
      Y1 := 0; Y2 := 15; 
   end; 
 
 // Combine the button image with Dest. 
   CombineImage2(Dest.Handle, Image_Controls.Handle, LocX, LocY, 
                 Rect(X1, Y1, X2, Y2), true{Transparent}); 
end; 
 
procedure TShowLyricsForm.DrawButtons(var Dest : TBitmap); 
begin 
   if ShowingBtns then 
   begin 
     PaintBtnImage(Dest, 99, Dest.Width - 43, 1, Last_Pressed, Last_Highlighted); 
   end; 
   PaintBtnImage(Dest, 98, Dest.Width - 22, 1, Last_Pressed, Last_Highlighted); 
end; 
 
procedure TShowLyricsForm.RelocateBtns(var BaseImage : TBitmap); 
begin 
 // 마우스에 반응하는 실제적인 버턴 역할을 하는 TImage 컴포넌트들을 버턴 이미지가 그려지는 위치에 둔다. 
   picShowMenu.BoundsRect := Rect(BaseImage.Width - 21, 3, BaseImage.Width + picShowMenu.Width - 21, picShowMenu.Height + 3); 
end; 
 
 
// 가사 표시가 바뀌어 화면을 갱신해야 할 때 호출하는 함수 (이 때는 버턴도 다시 그려야 한다) 
procedure TShowLyricsForm.RefreshWindow; 
var 
  DC         : HDC; 
  blendfunc  : BLENDFUNCTION; 
  srcPoint   : TPoint; 
  DestPoint  : TPoint; 
  winSize    : TSize; 
  BackImage : TBitmap; 
  SR, DR : TRect; 
begin 
   BackImage := TBitmap.Create; 
   BackImage.PixelFormat := pf32bit; 
 
   if ScaleFactor <> 1 then 
      GetResizedBitmap(TitleImage, BackImage, ScaleFactor) 
   else begin 
    // Assign 함수에 의한 이미지 복사는 에러를 유발하므로 CopyRect로 변경함 (2012-05-22) 
    //  BackImage.Assign(TitleImage); 
      BackImage.Width := TitleImage.Width; 
      BackImage.Height := TitleImage.Height; 
      SR := Rect(0, 0, TitleImage.Width, TitleImage.Height); 
      BackImage.Canvas.CopyRect(SR, TitleImage.Canvas, SR); 
   end; 
   BackImage2.Assign(BackImage); // 버턴이 그려지기 전의 상태, 즉, 가사만 그려진 이미지를 복사해 둔다. 
    
   if Assigned(Image_Controls) {and (not m_bStayOnVisWnd)} then 
   begin 
      DR := Rect(Image_BtnArea.Width - 130, 0, Image_BtnArea.Width - 5, Image_BtnArea.Height); 
      SR := Rect(BackImage.Width - 130, 1, BackImage.Width - 5, Image_BtnArea.Height + 1); 
    // Image_BtnArea Canvas에 가사가 그려진 상태의 이미지를 복사한다. 
      Image_BtnArea.Canvas.CopyRect(DR, BackImage.Canvas, SR); 
    // Image_BtnArea canvas에 버턴 이미지를 그린다. 
      DrawButtons(Image_BtnArea); 
    // 버턴이 그려진 Image_BtnArea 이미지를 BackImage로 복사한다. 
      BackImage.Canvas.CopyRect(SR, Image_BtnArea.Canvas, DR); // SR과 DR : 위치만 바꾸어 그대로 이용. 
   end; 
 
   DC := GetDC(0); 
   srcPoint.x := 0; 
   srcPoint.y := 0; 
   winSize.cx := BackImage.Width; 
   winSize.cy := BackImage.Height; 
   if Assigned(Image_Controls) then 
     if ScaleFactor <> Last_ScaleFactor then 
     begin 
       RelocateBtns(BackImage); 
       Last_ScaleFactor := ScaleFactor; 
     end; 
 
   DestPoint := BoundsRect.TopLeft; 
 
   With blendFunc do 
   begin 
     BlendOp := AC_SRC_OVER; 
     BlendFlags := 0;  // must be 0 
  // SourceConstantAlpha : the alpha transparency value to be used on entire source bitmap 
  // (0 ~ 255, 이 값이 255이면 blend 연산은 픽셀의 알파값만 이용하게 된다.) 
     SourceConstantAlpha := 255; 
     AlphaFormat := AC_SRC_ALPHA;  // source bitmap이 alpha channel을 가지고 있음을 나타냄. 
   end; 
 
   MyUpdateLayeredWindow(Handle, DC, @DestPoint, @winSize, BackImage.Canvas.Handle, 
                         @srcPoint, clBlack, @blendFunc, ULW_ALPHA); 
 
   ReleaseDC(0, DC); 
 
   BackImage.Free; 
   UpdatedLyricsImage := false; 
   UpdatedTitleImage := false; 
end; 
 
// 버턴 이미지만 바꾸어 표시해야 할 때 이용한다. 
procedure TShowLyricsForm.RefreshWindow2; 
var 
  DC         : HDC; 
  blendfunc  : BLENDFUNCTION; 
  srcPoint   : TPoint; 
  DestPoint  : TPoint; 
  winSize    : TSize; 
  SR, DR : TRect; 
  tmpImage : TBitmap; 
begin 
 // 투명도 유지를 위해 이미지 버퍼(tmpImage)를 이용하여 BackImage2는 버턴 이미지가 
 // 그려지지 않은 상태, 즉, 가사만 그려진 이미지 상태의 이미지를 유지하도록 한다. 
   tmpImage := TBitmap.Create; 
   tmpImage.PixelFormat := pf32bit; 
   tmpImage.Width := Image_BtnArea.Width; 
   tmpImage.Height := Image_BtnArea.Height; 
   SR := Rect(BackImage2.Width - 130, 1, BackImage2.Width - 5, Image_BtnArea.Height + 1); 
 // BackImage2의 버턴이 그려지는 영역을 이미지 버퍼에 복사해 둔다. 
   tmpImage.Canvas.CopyRect(Rect(0, 0, 125, Image_BtnArea.Height), BackImage2.Canvas, SR); 
 
   DR := Rect(Image_BtnArea.Width - 130, 0, Image_BtnArea.Width - 5, Image_BtnArea.Height); 
 // Image_BtnArea Canvas에 가사만 그려진 원래 상태의 이미지를 복사한다. 
   Image_BtnArea.Canvas.CopyRect(DR, BackImage2.Canvas, SR); 
 // Image_BtnArea canvas에 버턴 이미지를 그린다. 
   DrawButtons(Image_BtnArea); 
 // 버턴이 그려진 Image_BtnArea 이미지를 BackImage2로 복사한다. 
   BackImage2.Canvas.CopyRect(SR, Image_BtnArea.Canvas, DR);  // SR과 DR의 사용 위치만 바꾸어 그대로 이용함. 
 
   DC := GetDC(0); 
   srcPoint.x := 0; 
   srcPoint.y := 0; 
   winSize.cx := BackImage2.Width; 
   winSize.cy := BackImage2.Height; 
   DestPoint := BoundsRect.TopLeft; 
 
   With blendFunc do 
   begin 
     BlendOp := AC_SRC_OVER; 
     BlendFlags := 0;  // must be 0 
  // SourceConstantAlpha : the alpha transparency value to be used on entire source bitmap 
  // (0 ~ 255, 255 : blend 연산은 픽셀의 알파값만 이용) 
     SourceConstantAlpha := 255; 
     AlphaFormat := AC_SRC_ALPHA;  // source bitmap이 alpha channel을 가지고 있음을 나타냄. 
   end; 
 
   MyUpdateLayeredWindow(Handle, DC, @DestPoint, @winSize, BackImage2.Canvas.Handle, 
                         @srcPoint, clBlack, @blendFunc, ULW_ALPHA); 
 
   ReleaseDC(0, DC); 
 
  // 이미지 버퍼의 내용을 BackImage2에 복사하여 버턴 이미지가 그려지기 전의 원래 상태로 되돌린다. 
   BackImage2.Canvas.CopyRect(SR, tmpImage.Canvas, Rect(0, 0, 125, Image_BtnArea.Height)); 
   tmpImage.Free; 
end; 
 
procedure TShowLyricsForm.Timer1Timer(Sender: TObject); 
begin 
   if (Channel_Stat = plmPlaying) and (not UpdatedTitleImage)then 
      if Self.Visible then 
         if (SyncLyrics.NumLine > 0) then   // ** Added at Ver 1.2.7 
            DrawLyrics(false); 
 
   if (not UpdatedLyricsImage) and (not UpdatedTitleImage) then 
      exit; 
 
   RefreshWindow; 
end; 
 
 
procedure TShowLyricsForm.InitializeBackground(BackColor : TColor); 
var 
   P{, p2} : PARGBArray; 
   x, y : integer; 
   Brush_Background : TGPSolidBrush; 
   B_Red, B_Green, B_Blue : byte; 
 
begin 
   with TitleImage.Canvas do 
   begin 
     Brush.Style := bsSolid; 
     Brush.Color := clBlack; 
     FillRect(Rect(0, 0, TitleImage.Width, TitleImage.Height)); 
   end; 
 
   if BackColor <> 0 then 
   begin 
     B_Red   := GetRValue(BackColor); // = BackColor and $000000FF; 
     B_Green := GetGValue(BackColor); // = (BackColor and $0000FF00) div $00000100; 
     B_Blue  := GetBValue(BackColor); // = (BackColor and $00FF0000) div $00010000; 
     Brush_Background := TGPSolidBrush.Create(MakeColor(255, B_Red, B_Green, B_Blue)); 
     graphics.FillPath(Brush_Background, Path_Background); 
     Brush_Background.Free; 
   end; 
 
 // 투명도 반영 (브러쉬 자체에 투명도를 주면 색상이 틀리게 나오므로 FillPath 작업 후 보정한다) 
   if BackColor <> 0 then 
      for y := 0 to (TitleImage.Height - 1) do 
      begin 
         P := TitleImage.Scanline[y]; 
         for x := 0 to (TitleImage.Width - 1) do 
         begin   // * Changed at Ver 1.2.1 
            P[x].rgbRed := round(P[x].rgbRed * Bg_Transparency / 255); 
            P[x].rgbGreen := round(P[x].rgbGreen * Bg_Transparency / 255); 
            P[x].rgbBlue := round(P[x].rgbBlue * Bg_Transparency / 255); 
            P[x].rgbReserved := round(P[x].rgbReserved * Bg_Transparency / 255); 
         end; 
      end; 
 
 // RawBackImage.Assign(TitleImage); 
 // 위의 Assign문을 쓸 경우 배경이 보이지 않게 되므로 픽셀 단위로 복사하는 방법을 이용함. 
 //  ( Assign문은 alpha 값은 복사하지 않는지 ?? ) 
   BitBlt(RawBackImage.Canvas.Handle, 0, 0, RawBackImage.Width, RawBackImage.Height, 
                 TitleImage.Canvas.Handle, 0, 0, SRCCOPY); 
   BitBlt(ElapsedImage.Canvas.Handle, 0, 0, RawBackImage.Width, RawBackImage.Height, 
                 TitleImage.Canvas.Handle, 0, 0, SRCCOPY); 
 
  { for y := 0 to (TitleImage.Height - 1) do 
   begin 
     P2 := TitleImage.Scanline[y]; 
     P := RawBackImage.Scanline[y]; 
     for x := 0 to (TitleImage.Width - 1) do 
     begin 
        P[x].rgbRed := P2[x].rgbRed; 
        P[x].rgbGreen := P2[x].rgbGreen; 
        P[x].rgbBlue := P2[x].rgbBlue; 
        P[x].rgbReserved := P2[x].rgbReserved; 
     end; 
   end; } 
end; 
 
procedure TShowLyricsForm.SetImageBtn(img: TAdvImage); 
begin 
  img.Picture.Bitmap.Width := img.Width; 
  img.Picture.Bitmap.Height := img.Height; 
  img.Visible := true; 
  img.Enabled := true; 
end; 
 
procedure TShowLyricsForm.PrepareImageBtns; 
var 
 //  AWinHandle : THandle; 
   hbmReturn  : HBITMAP; 
   ProgDir : string; 
   S1, S2 : string; 
begin 
   ProgDir := ExtractFilePath(ParamStr(0)); 
 
 // 버턴 이미지를 읽어 들일 때 Opaque가 아닌 픽셀에 대해 혼합되는 색을 Blue로 한다. 
   hbmReturn := LoadSourceImage(ProgDir + 'skin\btns_Adjust.png', TColor($000000ff)); 
   if hbmReturn <> 0 then 
   begin 
      Image_Controls := TBitmap.Create; 
      Image_Controls.Handle := hbmReturn; 
   end else 
   begin 
     {$IFDEF MULTI_LANGUAGE} 
      S1 := _('Have not loaded the images for buttons on ShowLyricsForm.'); 
      S2 := _('Error'); 
      MessageBox(Handle, PChar(S1), PChar(S2), MB_OK); 
     {$ELSE} 
      MessageBox(Handle, 'Have not loaded the images for buttons on ShowLyricsForm.', 'Error', MB_OK); 
     {$ENDIF} 
      exit; 
   end; 
 
   SetImageBtn(picShowMenu); 
end; 
 
procedure TShowLyricsForm.FormCreate(Sender: TObject); 
begin 
  {$IFDEF MULTI_LANGUAGE} 
   TranslateComponent(self); 
  {$ENDIF} 
 //  NoLimitByFirstTime := true; 
   ScaleFactor := 1.0; 
   Bg_Transparency := Default_Transparency; 
  { if m_bStayOnVisWnd then 
      BackgroundColor := BackgroundColor_S 
   else } 
      BackgroundColor := BackgroundColor_F; 
 
   // 시스템에 가변폭 바른돋움 폰트가 설치되어 있는지 확인한다. 
  { if (GetOEMCP = 949) then 
     if Screen.Fonts.IndexOf('바른돋움Pro 2') = -1 then 
     begin 
       if AddFontResource('BareunDotumPro2.ttf') <> 0 then  // 폰트설치 성공 ? 
         SendMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0); 
     end; } 
 
   TitleAtStandby := _('Sync Lyrics Window'); 
   if (GetOEMCP = 949) and (Screen.Fonts.IndexOf('바른돋움Pro 2') <> -1) then 
   begin 
     LyricsFont := LyricsFont_1; 
     TitleFont := TitleFont_1; 
   end else if GetOEMCP = 949 then   // OEM code page is Korean Language ? 
   begin 
      LyricsFont := LyricsFont_3; 
      TitleFont := TitleFont_3; 
   end else 
   begin 
      LyricsFont := LyricsFont_2; 
      TitleFont := TitleFont_2; 
   end; 
 
  // 바른돋움 폰트는 글자사이에 간격이 없이 쓰여지므로 글자 사이 갭을 많이 준다. 
   if LyricsFont = '바른돋움Pro 2' then 
     Gap_B := 4        // space between characters for big size text 
   else 
     Gap_B := 2; 
   Gap_M := Gap_B * (TextHeight_M / TextHeight_B); // space between characters for medium size text 
   Gap_S := Gap_B * (TextHeight_S / TextHeight_B); // space between characters for small size text 
 
 // Tahoma 폰트는 글씨 영역 아랫 부분으로 글자 꼬리가 내려오므로 BorderMargin을 더 준다. 
   if LyricsFont = 'Tahoma' then 
     ImageBaseHeight := (LineHeight + BorderMargin) * 2 
   else 
     ImageBaseHeight := LineHeight * 2 + BorderMargin; 
 
   RawBackImage := TBitmap.Create; 
   RawBackImage.PixelFormat := pf32bit; 
   RawBackImage.Width := ImageBaseWidth; 
   RawBackImage.Height := ImageBaseHeight; 
 
 // Set up TitleImage for lyrics display 
   TitleImage := TBitmap.Create; 
   TitleImage.PixelFormat := pf32bit; 
   TitleImage.Width := ImageBaseWidth; 
   TitleImage.Height := ImageBaseHeight; 
 
   ElapsedImage := TBitmap.Create; 
   ElapsedImage.PixelFormat := pf32bit; 
   ElapsedImage.Width := ImageBaseWidth; 
   ElapsedImage.Height := ImageBaseHeight; 
 
   BackImage2 := TBitmap.Create; 
   BackImage2.PixelFormat := pf32bit; 
   BackImage2.Width := ImageBaseWidth; 
   BackImage2.Height := ImageBaseHeight; 
 
   Image_BtnArea := TBitmap.Create; 
   Image_BtnArea.PixelFormat := pf32bit; 
   Image_BtnArea.Width := 135; 
   Image_BtnArea.Height := 18; 
 
   PrepareGDIPElements; 
   PrepareImageBtns; 
   InitializeBackground(BackgroundColor); 
 
   m_bShown := false; 
   SyncLyrics.NumLine := 0; 
   FCurLine := -1; 
   StreamFormat := $10d00;   // 미디파일만 연주하는 것으로 간주 
 
   CURSOR_RESIZE := LoadCursor(0, IDC_SIZEWE); 
   SeparateWinAttr.ItemValid := false; 
end; 
 
procedure TShowLyricsForm.btnCloseClick(Sender: TObject); 
begin 
   Close; 
end; 
 
procedure TShowLyricsForm.MakeWndTransparent(Value : Boolean); 
var 
   curWinStyle: INT_PTR; 
begin 
  {$IF CompilerVersion < 20} 
   curWinStyle := GetWindowLong(Handle, GWL_EXSTYLE); 
   if Value then 
      SetWindowLong(Handle, GWL_EXSTYLE, curWinStyle or WS_EX_TRANSPARENT or WS_EX_LAYERED) 
   else 
      SetWindowLong(Handle, GWL_EXSTYLE, (curWinStyle and (not WS_EX_TRANSPARENT)) or WS_EX_LAYERED); 
  {$ELSE} 
   curWinStyle := GetWindowLongPtr(Handle, GWL_EXSTYLE); 
   if Value then 
      SetWindowLongPtr(Handle, GWL_EXSTYLE, curWinStyle or WS_EX_TRANSPARENT or WS_EX_LAYERED) 
   else 
      SetWindowLongPtr(Handle, GWL_EXSTYLE, (curWinStyle and (not WS_EX_TRANSPARENT)) or WS_EX_LAYERED); 
  {$IFEND} 
end; 
 
procedure TShowLyricsForm.FormShow(Sender: TObject); 
// var 
//   dwRecipients : DWORD; 
begin 
   if m_bShown then 
   begin 
      PostMessage(MainForm.Handle, WM_SyncLyricsConfig, SyncLyrics_Shown, 0); 
      exit; 
   end; 
 
   DoubleBuffered := true; 
 
   MakeWndTransparent(false); 
   SetWindowPos(Handle, HWND_TOPMOST, 100, 100, TitleImage.Width, TitleImage.Height, 0{SWP_NOMOVE or SWP_NOSIZE}); 
 
 //  Timer1.Enabled := true; 
 
   PostMessage(MainForm.Handle, WM_SyncLyricsConfig, SyncLyrics_Shown, 0); 
 
   m_bShown := true; 
end; 
 
procedure TShowLyricsForm.FormClose(Sender: TObject; 
  var Action: TCloseAction); 
begin 
   Timer1.Enabled := false; 
   PostMessage(MainForm.Handle, WM_SyncLyricsConfig, SyncLyrics_Closed, 0); 
end; 
 
procedure TShowLyricsForm.FormDestroy(Sender: TObject); 
begin 
   RawBackImage.Free; 
   TitleImage.Free; 
   ElapsedImage.Free; 
   BackImage2.Free; 
   Image_BtnArea.Free; 
   ReleaseGDIPElements; 
 //  ReleaseShareMem; 
 
   Image_Controls.Free; 
end; 
 
 
{ $F001 : 우측 고정 넓이 조절 
 $F002 : 좌측 고정 넓이 조절 
 $F003 : 하단 고정 높이 조절 
 $F004 : 우하단 고정 전체 크기 조절 
 $F005 : 좌하단 고정 전체 크기 조절 
 $F006 : 상단 고정 높이 조절 
 $F007 : 우상단 고정 전체 크기 조절 
 $F008 : 좌상단 고정 전체 크기 조절 
 $F009, $F012 : 이동 } 
 
procedure TShowLyricsForm.FormMouseDown(Sender: TObject; 
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer); 
begin 
   if Button = mbLeft then 
   begin 
      Old_LocX := X;      // Store current position 
      Old_LocY := Y; 
 
      if ((Y > 5) and (Y < (Self.Height - 6))) and ({(X < 3) or} (X > (Self.Width - 4))) then 
      begin 
         if (X < 3) then 
            MouseLeftSide := true 
         else 
            MouseLeftSide := false; 
         SetCursor(CURSOR_RESIZE); 
         IsSizing := true; 
 
        { ReleaseCapture; 
         if MouseLeftSide then 
            Perform(wm_syscommand, $f001, 0) 
         else 
            Perform(wm_syscommand, $f002, 0); } 
      end else 
      begin 
         IsMoving := true; 
         ReleaseCapture; 
         Perform( WM_SYSCOMMAND, $F012, 0 ); 
      end; 
   end; 
end; 
 
procedure TShowLyricsForm.FormMouseMove(Sender: TObject; 
  Shift: TShiftState; X, Y: Integer); 
var 
   NewScaleFactor : single; 
   NewWidth : integer; 
 
begin 
  { if IsMoving then 
      Self.SetBounds(Self.Left + X - Old_LocX, Self.Top + Y - Old_LocY, 
                     Self.Width, Self.Height) 
   else} if IsSizing then 
   begin 
      if MouseLeftSide then 
      begin 
         NewWidth := Self.Width + Old_LocX - X; 
         Self.Left := Self.Left + X - Old_LocX; 
      end else 
         NewWidth := Self.Width - Old_LocX + X; 
 
      NewScaleFactor := NewWidth / ImageBaseWidth; 
      if NewScaleFactor < MinScaleFactor then 
      begin 
         if ScaleFactor = MinScaleFactor then 
            exit; 
         ScaleFactor := MinScaleFactor; 
      end 
      else if NewScaleFactor > MaxScaleFactor then 
      begin 
         if ScaleFactor = MaxScaleFactor then 
            exit; 
         ScaleFactor := MaxScaleFactor; 
      end else 
         ScaleFactor := NewScaleFactor; 
 
      RefreshWindow; 
   end 
   else if ((Y > 5) and (Y < (Self.Height - 6))) and ({(X < 3) or} (X > (Self.Width - 4))) then 
     SetCursor(CURSOR_RESIZE); 
end; 
 
procedure TShowLyricsForm.FormMouseUp(Sender: TObject; 
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer); 
var 
   p: TPoint; 
begin 
   if (Button = mbRight) then 
   begin 
      GetCursorPos(p); 
      PopupMenu1.Popup(p.x, p.y); 
   end 
   else if Button = mbLeft then 
   begin 
      if IsSizing then 
      begin 
         IsSizing := false;      // Stop Resizing 
      // MainForm으로 Focus를 옮기는 것은 크기 조정 후 마우스 해제 시 마우스 커서 위치가 
      // 틀리게 인식되는 것을 막기 위해서임. 
         MainForm.SetFocus; 
      end else 
      if IsMoving then 
         IsMoving := false;      // Stop Movement 
   end; 
end; 
 
procedure TShowLyricsForm.menuAdjustLyricsClick(Sender: TObject); 
begin 
   LyricsAdjustForm.LyricsHandle := Self.Handle; 
   LyricsAdjustForm.Transparency := 255 - Bg_Transparency; 
   LyricsAdjustForm.ScaleFactor  := ScaleFactor; 
   LyricsAdjustForm.Show; 
end; 
 
procedure TShowLyricsForm.menuShowSyncSetFormClick(Sender: TObject); 
begin 
   SetSyncShiftTimeForm.Show; 
end; 
 
procedure TShowLyricsForm.menuCloseClick(Sender: TObject); 
begin 
   Close; 
end; 
 
{ 
procedure TShowLyricsForm.menuMergeIntoVisWindowClick(Sender: TObject); 
begin 
   if not m_bStayOnVisWnd then 
   begin 
      SetLyricsStayOnVisWnd(true); 
      EMBEDWidthChange; 
      Self.Top := Vis_LyricsDrawer.Top + Vis_LyricsDrawer.Height 
                - round(ScaleFactor * TitleImage.Height) - 16; 
      Self.Left := Vis_LyricsDrawer.Left 
                 - round(ScaleFactor * TitleImage.Width - Vis_LyricsDrawer.Width) div 2 + 1; 
   end; 
end; } 
 
 
procedure TShowLyricsForm.ReloadBtnImage(Btn_No : integer; b_Pressed, b_Highlighted : boolean); 
begin 
   if b_Pressed then begin 
      Last_Pressed := Btn_No; 
      Last_Highlighted := 0; 
   end else if b_Highlighted then begin 
      Last_Pressed := 0; 
      Last_Highlighted := Btn_No 
   end else begin 
      Last_Pressed := 0; 
      Last_Highlighted := 0; 
   end; 
 
 // 동기가사창이 Merge된 이후에 OnMouseLeave Event가 발생하는 수가 있으므로 사전에 필터링하지 
 // 않으면 버턴이 보이게 된다. 
 //  if (not m_bStayOnVisWnd) then 
      RefreshWindow2; 
end; 
 
procedure TShowLyricsForm.picButtonMouseEnter(Sender: TObject); 
begin 
  i_CursorOn := (Sender as TComponent).Tag; 
 
  if b_BtnPressed then 
     exit; 
 
  ReloadBtnImage((Sender as TComponent).Tag, false, true); 
end; 
 
procedure TShowLyricsForm.picButtonMouseLeave(Sender: TObject); 
begin 
  if (Sender as TComponent).Tag = i_CursorOn then 
     i_CursorOn := 0; 
 
  ReloadBtnImage((Sender as TComponent).Tag, false, false); 
 
  if b_BtnPressed then 
     b_BtnPressed := false; 
end; 
 
procedure TShowLyricsForm.picButtonMouseDown(Sender: TObject; 
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer); 
begin 
   b_BtnPressed := true; 
   SetCapture(Self.Handle); 
 
  // ReloadBtnImage(Btn_No : integer; b_Pressed, b_Highlighted : boolean); 
   ReloadBtnImage((Sender as TComponent).Tag, true, false); 
 
   if i_CursorOn <>(Sender as TComponent).Tag then   // 버턴 동작에 응답하지 않는 경우에 대비 
      i_CursorOn :=(Sender as TComponent).Tag; 
end; 
 
procedure TShowLyricsForm.picButtonMouseUp(Sender: TObject; 
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer); 
begin 
   b_BtnPressed := false; 
 
   ReleaseCapture; 
   if (Sender as TComponent).Tag = i_CursorOn then 
   begin 
      ReloadBtnImage((Sender as TComponent).Tag, false, true); 
      case (Sender as TComponent).Tag of 
         98 : PopupMenu1.Popup(Self.Left + picShowMenu.Left - 150, Self.Top + 20); 
      end; 
   end else 
      ReloadBtnImage((Sender as TComponent).Tag, false, false); 
end; 
 
end.