unit MergeUnit;
// PicViewer (c) J. Dempster, University of Strathclyde, 2003
// ----------------------------------------------------------
// RGB Merge : Merges grey scale stacks into RGB file
// ----------------------------------------------------------
// 3.11.03
// 17.2.4 OK button re-enabled after merge
// 23.3.4 Frame merge facility added
// 7.05.04 Method of frame merging changed
// 04.09.04 Pre-existing form now closed automatically
// 30.03.05 Merged file name now file names added together
//          and bug in file naming fixed
// 12.11.05 Bugs which prevented G+B merging fixed

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ImageFile, StdCtrls, ValidatedEdit, ExtCtrls, StrUtils ;

type
  TMergeFrm = class(TForm)
    SourceGrp: TGroupBox;
    bCancel: TButton;
    bOK: TButton;
    SaveFile: TImageFile;
    GroupBox2: TGroupBox;
    edFileName: TEdit;
    edGreenFrame: TValidatedEdit;
    edBlueFrame: TValidatedEdit;
    edRedFrame: TValidatedEdit;
    lbRedFrame: TLabel;
    Label2: TLabel;
    cbRedSource: TComboBox;
    Label1: TLabel;
    cbGreenSource: TComboBox;
    Label3: TLabel;
    cbBlueSource: TComboBox;
    lbGreenFrame: TLabel;
    lbBlueFrame: TLabel;
    procedure FormShow(Sender: TObject);
    procedure bOKClick(Sender: TObject);
    procedure cbRedSourceChange(Sender: TObject);
    procedure bCancelClick(Sender: TObject);
  private
    { Private declarations }
    procedure CreateLUT(
              GreyMax : Integer ;
              GreyLo : Integer ;
              GreyHi : Integer ;
              var LUT : Array of Byte
              ) ;
    procedure SourceChanged ;
    procedure MergeFiles ;

  public
    { Public declarations }
  end;

var
  MergeFrm: TMergeFrm;

implementation

uses PicViewMain, ViewUnit, maths ;

const
     NoFileSelected = 9999 ;

{$R *.dfm}

procedure TMergeFrm.FormShow(Sender: TObject);
// -------------------------------------
// Initialisation when form is displayed
// -------------------------------------
var
     i : Integer ;
begin

     // Get list of available image stacks
     cbRedSource.Clear ;
     cbRedSource.Items.AddObject( ' ', TObject(NoFileSelected)) ;
     cbGreenSource.Clear ;
     cbGreenSource.Items.AddObject( ' ', TObject(NoFileSelected)) ;
     cbBlueSource.Clear ;
     cbBlueSource.Items.AddObject( ' ', TObject(NoFileSelected)) ;
     for i :=  0 to High(MainFrm.ViewFrmsInUse) do
         if MainFrm.ViewFrmsInUse[i] then begin
         if MainFrm.ViewFrms[i].NumComponentsPerPixel = 1 then begin
            cbRedSource.Items.AddObject( MainFrm.ViewFrms[i].FileName, TObject(i)) ;
            cbGreenSource.Items.AddObject( MainFrm.ViewFrms[i].FileName, TObject(i)) ;
            cbBlueSource.Items.AddObject( MainFrm.ViewFrms[i].FileName, TObject(i)) ;
            end ;
         end ;
     cbRedSource.ItemIndex := 0 ;
     cbGreenSource.ItemIndex := 0 ;
     cbBlueSource.ItemIndex := 0 ;

     edRedFrame.Value := 1 ;
     edGreenFrame.Value := 1 ;
     edBlueFrame.Value := 1 ;

     // Set controls
     SourceChanged ;

     bOK.Enabled := True ;

     end;



procedure TMergeFrm.bOKClick(Sender: TObject);
// ------------------------
// Create a merged RGB file
// ------------------------
begin
     // Prevent multiple activation
     bOK.Enabled := False ;
     Application.ProcessMessages ;

     // Merge from separate files or frames
     MergeFiles ;

     bOK.Enabled := True ;

     end ;


procedure TMergeFrm.MergeFiles ;
// --------------------------------------
// Merge 2 or more files into an RGB file
// --------------------------------------
const
    MaxSources = 3 ;
var
     FileName : String ;  // Source file name

     iRed, iGreen, iBlue : Integer ; // Source image index numbers
     iFile : Integer ;

     i,j : Integer ;           // General counter
     iCol : Integer ;
     FrameNum : Integer ;    // Source frame counter

     NumSources : Cardinal ;
     FrameWidth : Array[0..MaxSources-1] of Cardinal ;
     FrameHeight :Array[0..MaxSources-1] of Cardinal ;
     NumStacks :Array[0..MaxSources-1] of Cardinal ;
     NumSectionsPerStack :Array[0..MaxSources-1] of Cardinal ;

     NumPixelsPerFrame : Cardinal ;
     NumComponentsPerPixel : Cardinal ;
     PixelDepth : Cardinal ;
     NumComponentsPerFrame : Cardinal ;
     StackNum : Cardinal ;

     XResolution : Single ;       // Pixel width
     YResolution : Single ;       // Pixel height
     ZResolution : Single ;       // Pixel depth
     TResolution : Single ;       // Inter-frame interval
     PixelUnits : string ;       // Pixel width units

     OK : Boolean ;

     PRedBuf : PIntArray ;       // Red frame buffer pointer
     PGreenBuf : PIntArray ;       // Green frame buffer pointer
     PBlueBuf : PIntArray ;       // Blue frame buffer pointer
     PRGBBuf :  PByteArray ;       // RGB frame buffer pointer
     PRedLUT : PByteArray ;       // Red look up table pointer
     PGreenLUT : PByteArray ;     // Green look up table pointer
     PBlueLUT : PByteArray ;      // Blue look up table pointer

begin

     // Source of image stacks to be merged
     iRed := Integer(cbRedSource.Items.Objects[cbRedSource.ItemIndex]) ;
     iGreen := Integer(cbGreenSource.Items.Objects[cbGreenSource.ItemIndex]) ;
     iBlue := Integer(cbBlueSource.Items.Objects[cbBlueSource.ItemIndex]) ;

     FileName := edFileName.Text ;

     // Get image properties

     NumSources := 0 ;

     // Get red channel data
     if iRed <> NoFileSelected then begin
        FrameWidth[NumSources] := MainFrm.ViewFrms[iRed].FrameWidth ;
        FrameHeight[NumSources] := MainFrm.ViewFrms[iRed].FrameHeight ;
        NumStacks[NumSources] := MainFrm.ViewFrms[iRed].NumStacks ;
        NumSectionsPerStack[NumSources] := MainFrm.ViewFrms[iRed].NumSectionsPerStack ;
        XResolution := MainFrm.ViewFrms[iRed].XResolution ;
        YResolution := MainFrm.ViewFrms[iRed].YResolution ;
        ZResolution := MainFrm.ViewFrms[iRed].ZResolution ;
        TResolution := MainFrm.ViewFrms[iRed].TResolution ;
        PixelUnits := MainFrm.ViewFrms[iRed].PixelUnits ;
        Inc(NumSources) ;
        end ;

     // Get green channel data
     if iGreen <> NoFileSelected then begin
        FrameWidth[NumSources] := MainFrm.ViewFrms[iGreen].FrameWidth ;
        FrameHeight[NumSources] := MainFrm.ViewFrms[iGreen].FrameHeight ;
        NumStacks[NumSources] := MainFrm.ViewFrms[iGreen].NumStacks ;
        NumSectionsPerStack[NumSources] := MainFrm.ViewFrms[iGreen].NumSectionsPerStack ;
        XResolution := MainFrm.ViewFrms[iGreen].XResolution ;
        YResolution := MainFrm.ViewFrms[iGreen].YResolution ;
        ZResolution := MainFrm.ViewFrms[iGreen].ZResolution ;
        TResolution := MainFrm.ViewFrms[iGreen].TResolution ;
        PixelUnits := MainFrm.ViewFrms[iGreen].PixelUnits ;
        Inc(NumSources) ;
        end ;

     // Get green channel data
     if iBlue <> NoFileSelected then begin
        FrameWidth[NumSources] := MainFrm.ViewFrms[iBlue].FrameWidth ;
        FrameHeight[NumSources] := MainFrm.ViewFrms[iBlue].FrameHeight ;
        NumStacks[NumSources] := MainFrm.ViewFrms[iBlue].NumStacks ;
        NumSectionsPerStack[NumSources] := MainFrm.ViewFrms[iBlue].NumSectionsPerStack ;
        XResolution := MainFrm.ViewFrms[iBlue].XResolution ;
        YResolution := MainFrm.ViewFrms[iBlue].YResolution ;
        ZResolution := MainFrm.ViewFrms[iBlue].ZResolution ;
        TResolution := MainFrm.ViewFrms[iBlue].TResolution ;
        PixelUnits := MainFrm.ViewFrms[iBlue].PixelUnits ;
        Inc(NumSources) ;
        end ;

     if NumSources <= 0 then begin
        ShowMessage( 'RGB Merge: No images available!' ) ;
        Exit ;
        end ;

     // Check for image size mismatch
     OK := True ;
     for i := 0 to NumSources-1 do begin
         if FrameWidth[i] <> FrameWidth[0] then OK := False ;
         if FrameHeight[i] <> FrameHeight[0] then OK := False ;
         if FrameHeight[i] <> FrameHeight[0] then OK := False ;
         if NumSectionsPerStack[i] <> NumSectionsPerStack[0] then OK := False ;
         end ;

     if not OK then begin
        ShowMessage( 'RGB Merge: Image height/widths/no. frames mis-matched!') ;
        Exit ;
        end ;

     NumPixelsPerFrame := FrameWidth[0]*FrameHeight[0] ;
     NumComponentsPerPixel := 3 ;
     PixelDepth := 8 ;
     NumComponentsPerFrame := NumPixelsPerFrame*NumComponentsPerPixel ;

     // Let user quit if merge file already exists
     if FileExists(FileName) then begin
        if MessageDlg( 'RGB Merge: ' + FileName + ' already exists! Overwrite it?',
           mtWarning, [mbYes,mbNo], 0 ) = mrNo then Exit ;
        end ;

     // Close form (if output file is on display)
     MainFrm.CloseViewFrm(FileName);

     OK := SaveFile.CreateFile( FileName,
                                FrameWidth[0],
                                FrameHeight[0],
                                PixelDepth,
                                NumComponentsPerPixel,
                                False ) ;
     if not OK then Exit ;

     SaveFile.XResolution := XResolution ;
     SaveFile.YResolution := YResolution ;
     SaveFile.ZResolution := ZResolution ;
     SaveFile.TResolution := TResolution ;
     SaveFile.ResolutionUnit := PixelUnits ;

     // Allocate frame buffers
     GetMem( PRedBuf, NumPixelsPerFrame*4 ) ;
     GetMem( PGreenBuf, NumPixelsPerFrame*4 ) ;
     GetMem( PBlueBuf, NumPixelsPerFrame*4 ) ;
     GetMem( PRGBBuf, NumComponentsPerFrame ) ;
     // Create look up tables
     GetMem( PRedLUT, (LUTMax+1) ) ;
     GetMem( PGreenLUT, (LUTMax+1) ) ;
     GetMem( PBlueLUT, (LUTMax+1) ) ;

     // Fill grey-scale -> RGB look up tables
     if iRed <> NoFileSelected then begin
        CreateLUT( MainFrm.ViewFrms[iRed].GreyMax,
                   MainFrm.ViewFrms[iRed].GreyLo,
                   MainFrm.ViewFrms[iRed].GreyHi,
                   PRedLUT^ ) ;
        end
     else begin
         for i := 0 to LUTMax do PRedLUT^[i] := 0 ;
         for i := 0 to NumPixelsPerFrame-1 do PRedBuf^[i] := 0 ;
         end ;

     if iGreen <> NoFileSelected then begin
        CreateLUT( MainFrm.ViewFrms[iGreen].GreyMax,
                   MainFrm.ViewFrms[iGreen].GreyLo,
                   MainFrm.ViewFrms[iGreen].GreyHi,
                   PGreenLUT^ ) ;
        end
     else begin
         for i := 0 to LUTMax do PGreenLUT^[i] := 0 ;
         for i := 0 to NumPixelsPerFrame-1 do PGreenBuf^[i] := 0 ;
         end ;

     if iBlue <> NoFileSelected then begin
        CreateLUT( MainFrm.ViewFrms[iBlue].GreyMax,
                   MainFrm.ViewFrms[iBlue].GreyLo,
                   MainFrm.ViewFrms[iBlue].GreyHi,
                   PBlueLUT^ ) ;
        end
     else begin
        for i := 0 to LUTMax do PBlueLUT^[i] := 0 ;
        for i := 0 to NumPixelsPerFrame-1 do PBlueBuf^[i] := 0 ;
        end ;

     try

        // If source files are different stack series
        // ignore section numbers and merge all sections

        if NumSectionsPerStack[0] > 1 then begin
            if (iRed <> NoFileSelected) then iFile := iRed
            else if (iGreen <> NoFileSelected) then iFile := iGreen
            else if (iBlue <> NoFileSelected) then iFile := iBlue ;
            if (iRed <> iFile) or (iBlue <> iFile) or (iGreen <> iFile) then begin
               NumStacks[0] := NumStacks[0]*NumSectionsPerStack[0] ;
               NumSectionsPerStack[0] := 1 ;
               edRedFrame.Value := 1.0 ;
               edBlueFrame.Value := 1.0 ;
               edGreenFrame.Value := 1.0 ;
               end ;
            end ;

        for StackNum := 1 to NumStacks[0] do begin

            // Load frames from source file
            if iRed <> NoFileSelected then begin
               FrameNum := (StackNum-1)*NumSectionsPerStack[0] + Round(edRedFrame.Value) ;
               MainFrm.ViewFrms[iRed].LoadFrame( FrameNum, PRedBuf ) ;
               end ;

            if iGreen <> NoFileSelected then begin
               FrameNum := (StackNum-1)*NumSectionsPerStack[0] + Round(edGreenFrame.Value) ;
               MainFrm.ViewFrms[iGreen].LoadFrame( FrameNum, PGreenBuf ) ;
               end ;

            if iBlue <> NoFileSelected then begin
               FrameNum := (StackNum-1)*NumSectionsPerStack[0] + Round(edGreenFrame.Value) ;
               MainFrm.ViewFrms[iBlue].LoadFrame( FrameNum, PBlueBuf ) ;
               end ;

            // Copy into RGB buffer
            j := 0 ;
            for i := 0 to NumPixelsPerFrame-1 do begin
                PRGBBuf^[j] := PRedLUT^[PRedBuf^[i]] ;
                Inc(j) ;
                PRGBBuf^[j] := PGreenLUT^[PGreenBuf^[i]] ;
                Inc(j) ;
                PRGBBuf^[j] := PBlueLUT^[PBlueBuf^[i]] ;
                Inc(j) ;
                end ;

            // Save image to destination
            SaveFile.SaveFrame( StackNum, PRGBBuf ) ;

            // Report progress
            MainFrm.StatusBar.SimpleText := format(
            'Merge to RGB: Frame %.4d/%.4d to %s',
            [StackNum,NumStacks[0],ExtractFileName(FileName)] ) ;

            if bOK.Enabled then Break ;
            Application.ProcessMessages ;

            end ;

        // Close file
        SaveFile.CloseFile ;

        MainFrm.StatusBar.SimpleText := format(
        'Merge to RGB: File: %s created',
        [ExtractFileName(FileName)] ) ;

        // Display Z projection image
        MainFrm.CreateNewViewFrm( FileName ) ;

     finally
        FreeMem( PRedBuf ) ;
        FreeMem( PGreenBuf ) ;
        FreeMem( PBlueBuf ) ;
        FreeMem( PRGBBuf ) ;
        FreeMem( PRedLUT ) ;
        FreeMem( PGreenLUT ) ;
        FreeMem( PBlueLUT ) ;

        end ;

     end ;


procedure TMergeFrm.CreateLUT(
          GreyMax : Integer ;
          GreyLo : Integer ;
          GreyHi : Integer ;
          var LUT : Array of Byte
          ) ;
// ----------------------------
// Create look-up table
// ----------------------------
const
   MinColor = 0 ;
   MaxColor = 255 ;
var
    i,y : Integer ;
    GreyScale : Single ;
begin

     if GreyHi <> GreyLo then GreyScale := MaxColor / (GreyHi - GreyLo)
                         else GreyScale := 1.0 ;

     for i := 0 to GreyMax do begin
         y := MinColor + Round((i-GreyLo)*GreyScale) ;
         if y < MinColor then y := MinColor ;
         if y > MaxColor then y := MaxColor ;
         LUT[i] := y ;
         end ;

     end ;


procedure TMergeFrm.cbRedSourceChange(Sender: TObject);
// ---------------------------------------------
// Create name of merge file when source changed
// ---------------------------------------------
begin
     SourceChanged ;
     end ;


procedure TMergeFrm.SourceChanged ;
// ---------------------------------------------
// Create name of merge file when source changed
// ---------------------------------------------

var
     iRed, iGreen, iBlue : Integer ; // Source image index numbers
     i,j : Integer ;           // General counter
     iCol : Integer ;
     FileName : String ;
     AddName : String ;
begin

     bOK.Enabled := False ;

     // Source of image stacks to be merged
     iRed := Integer(cbRedSource.Items.Objects[cbRedSource.ItemIndex]) ;
     iGreen := Integer(cbGreenSource.Items.Objects[cbGreenSource.ItemIndex]) ;
     iBlue := Integer(cbBlueSource.Items.Objects[cbBlueSource.ItemIndex]) ;



     // Create merge file (RGB TIF)
     iCol := MinInt([iRed,iGreen,iBlue]) ;
     if iCol <> NoFileSelected then begin
        FileName := '' ;
        i := 1 ;
        While (MainFrm.ViewFrms[iCol].FileName[i] <> '.') and
              (i <= Length(MainFrm.ViewFrms[iCol].FileName)) do begin
              FileName := FileName + MainFrm.ViewFrms[iCol].FileName[i] ;
              Inc(i) ;
              end ;
        FileName := FileName + '[Merge].tif' ;
        bOK.Enabled := True ;
        end
     else begin
          FileName := '' ;
          bOK.Enabled := False ;
          end ;
     edFileName.Text := FileName ;

     FileName := '' ;

     // Red source

     edRedFrame.Visible := False ;
     lbRedFrame.Visible := edRedFrame.Visible ;
     cbRedSource.Width := edRedFrame.Left + edRedFrame.Width - cbRedSource.Left - 5 ;
     edRedFrame.Value := 1 ;
     if (iRed <> NoFileSelected) then begin
        // Set output file name to red file without extension
        FileName := ANSIReplaceText( MainFrm.ViewFrms[iRed].FileName,
                                     ExtractFileExt(MainFrm.ViewFrms[iRed].FileName), '') ;
        if MainFrm.ViewFrms[iRed].NumSectionsPerStack > 1 then begin
           edRedFrame.Visible := True ;
           lbRedFrame.Visible := edRedFrame.Visible ;
           cbRedSource.Width := lbRedFrame.Left - cbRedSource.Left - 5 ;
           edRedFrame.HiLimit := MainFrm.ViewFrms[iRed].NumSectionsPerStack ;
           edRedFrame.Value := 1 ;
           end ;
        end ;

     // Green source

     edGreenFrame.Visible := False ;
     lbGreenFrame.Visible := edGreenFrame.Visible ;
     cbGreenSource.Width := edGreenFrame.Left + edGreenFrame.Width - cbGreenSource.Left - 5 ;
     edGreenFrame.Value := 1 ;
     if iGreen <> NoFileSelected then begin

        // Add green image to output file name
        if FileName = '' then begin
           FileName := ANSIReplaceText( MainFrm.ViewFrms[iGreen].FileName,
                                      ExtractFileExt(MainFrm.ViewFrms[iGreen].FileName), '') ;
           end
        else begin
           // Add name portion to existing file
           AddName := ANSIReplaceText( MainFrm.ViewFrms[iGreen].FileName,
                                     ExtractFileExt(MainFrm.ViewFrms[iGreen].FileName), '') ;
           AddName := ANSIReplaceText( AddName,
                                     ExtractFilePath(MainFrm.ViewFrms[iGreen].FileName), '') ;
           FileName := FileName + ' + ' + AddName ;
           end ;

        if MainFrm.ViewFrms[iGreen].NumSectionsPerStack > 1 then begin
           edGreenFrame.Visible := True ;
           lbGreenFrame.Visible := edGreenFrame.Visible ;
           cbGreenSource.Width := lbGreenFrame.Left - cbGreenSource.Left - 5 ;
           edGreenFrame.HiLimit := MainFrm.ViewFrms[iGreen].NumSectionsPerStack ;
           edGreenFrame.Value := MinInt([2,MainFrm.ViewFrms[iGreen].NumSectionsPerStack]) ;
           end ;
        end ;

     // Blue source

     edBlueFrame.Visible := False ;
     lbBlueFrame.Visible := edBlueFrame.Visible ;
     cbBlueSource.Width := edBlueFrame.Left + edBlueFrame.Width - cbBlueSource.Left - 5 ;
     edBlueFrame.Value := 1 ;
     if iBlue <> NoFileSelected then begin

        // Add Blue image to output file name
        if FileName = '' then begin
           FileName := ANSIReplaceText( MainFrm.ViewFrms[iBlue].FileName,
                                      ExtractFileExt(MainFrm.ViewFrms[iBlue].FileName), '') ;
           end
        else begin
           // Add name portion to existing file
           AddName := ANSIReplaceText( MainFrm.ViewFrms[iBlue].FileName,
                                     ExtractFileExt(MainFrm.ViewFrms[iBlue].FileName), '') ;
           AddName := ANSIReplaceText( AddName,
                                     ExtractFilePath(MainFrm.ViewFrms[iBlue].FileName), '') ;
           FileName := FileName + ' + ' + AddName ;
           end ;

        if MainFrm.ViewFrms[iBlue].NumSectionsPerStack > 1 then begin
           edBlueFrame.Visible := True ;
           lbBlueFrame.Visible := edBlueFrame.Visible ;
           cbBlueSource.Width := lbBlueFrame.Left - cbBlueSource.Left - 5 ;
           edBlueFrame.HiLimit := MainFrm.ViewFrms[iBlue].NumSectionsPerStack ;
           edBlueFrame.Value := MinInt([3,MainFrm.ViewFrms[iBlue].NumSectionsPerStack]) ; ;
           end ;
        end ;

     FileName := FileName + ' [Merge].tif' ;
     edFileName.Text := FileName ;

     end;

procedure TMergeFrm.bCancelClick(Sender: TObject);
//  ------------------------------
// Cancel merge / close form
// ------------------------------
begin
     bOK.Enabled := True ;
     end;


end.
