unit Filters;

{}

interface


	uses
		Types, Memory, QuickDraw, QuickDrawText, Packages, Menus, Events, Fonts, 
		Scrap, ToolUtils, Resources, Errors, Palettes, StandardFile, Windows,
		Controls, TextEdit, Files, Dialogs, TextUtils, Finder, MixedMode, Processes,
		globals, Utilities, Graphics, File1, File2, Analysis, Camera, Lut, realUtils, Text;


	procedure ApplyLookupTable;
	procedure MakeBinary;
	procedure Filter (ftype: FilterType; pass: integer; var table: FateTable);
	procedure PhotoMode;
	function AllSameSize: boolean;
	procedure EnhanceContrast;
	procedure EqualizeHistogram;
	procedure Convolve (name: str255; RefNum: integer);
	procedure ConvolveUsingText;
	procedure PlotSurface;
	procedure MakeSkeleton;
	procedure DoErosion;
	procedure DoDilation;
	procedure DoOpening;
	procedure DoClosing;
	procedure SetBinaryCount;
	procedure SetIterations;
	procedure ChangeValues (v1, v2, v3: integer);
	procedure DoPropagate (MenuItem: integer);
	procedure NewPlotSurface;
	procedure AutoThreshold;
	procedure AutoDensitySlice;
	procedure ScaleAndRotate;
	procedure DoRankFilter;
	procedure DoShadowFilter;
	procedure MakeEDM(item: integer);


implementation

	const
		MaxW = 4000;

	type
		ktype = array[0..MaxW] of integer;
		SortArray = array[1..9] of integer;
		StartsArray = array[0..255] of LongInt;

	var
		PixelsRemoved: LongInt;
		DebugCount: integer;


{$PUSH}
{$D-}

	function DoApplyTableDialogBox: boolean;
		const
			Button1 = 3;
			Button2 = 4;
			Button3 = 5;
			Button4 = 6;
		var
			mylog: DialogPtr;
			item: integer;
			SaveA, SaveB: boolean;

		procedure SetButtons;
		begin
			SetDlogItem(mylog, Button1, ord(ThresholdToForeground));
			SetDlogItem(mylog, Button2, ord(not ThresholdToForeground));
			SetDlogItem(mylog, Button3, ord(NonThresholdToBackground));
			SetDlogItem(mylog, Button4, ord(not NonThresholdToBackground));
		end;

	begin
		InitCursor;
		SaveA := ThresholdToForeground;
		SaveB := NonThresholdToBackground;
		mylog := GetNewDialog(40, nil, pointer(-1));
		SetButtons;
		OutlineButton(MyLog, ok, 16);
		repeat
			ModalDialog(nil, item);
			if (item = Button1) or (item = button2) then begin
					ThresholdToForeground := not ThresholdToForeground;
					SetButtons;
				end;
			if (item = Button3) or (item = button4) then begin
					NonThresholdToBackground := not NonThresholdToBackground;
					SetButtons;
				end;
		until (item = ok) or (item = cancel);
		DisposeDialog(mylog);
		if item = cancel then begin
				ThresholdToForeground := SaveA;
				NonThresholdToBackground := SaveB;
				DoApplyTableDialogBox := false
			end
		else
			DoApplyTableDialogBox := true;
	end;


	procedure ApplyLookupTable;
		var
			table: LookupTable;
			ConvertingColorPic, GrayScaleImage: boolean;
			i: integer;
	begin
		with info^ do begin
				GrayScaleImage := (LUTMode = Grayscale) or (LUTMode = CustomGrayscale);
				ConvertingColorPic := not GrayScaleImage and not DensitySlicing;
				if ConvertingColorPic then
					KillRoi;
				if DensitySlicing and (not macro) then begin
						if not DoApplyTableDialogBox then
							exit(ApplyLookupTable);
					end;
				if thresholding then
					BinaryPic := true;
				GetLookupTable(table);
				if GrayscaleImage or ConvertingColorPic then
					ResetGrayMap;
				ApplyTable(table);
				if ConvertingColorPic then
					WhatToUndo := NothingToUndo;
				if fit <> uncalibrated then begin
						fit := uncalibrated;
						for i := 0 to 255 do
							cvalue[i] := i;
					end;
			end; {with}
	end;


	procedure MakeBinary;
		var
			table: LookupTable;
			SaveBackground, SaveForeground, i: integer;
	begin
		with info^ do begin
				if DensitySlicing then begin
						ThresholdToForeground := true;
						NonThresholdToBackground := true;
						SaveBackground := BackgroundIndex;
						SaveForeground := ForegroundIndex;
						BackgroundIndex := WhiteIndex;
						ForegroundIndex := BlackIndex;
						GetLookupTable(table);
						ResetGrayMap;
						ApplyTable(table);
						BackgroundIndex := SaveBackground;
						ForegroundIndex := SaveForeground;
						BinaryPic := true;
					end
				else if Thresholding then begin
						for i := 0 to 255 do
							if i < ColorStart then
								table[i] := WhiteIndex
							else
								table[i] := BlackIndex;
						ResetGrayMap;
						ApplyTable(table);
						BinaryPic := true;
					end
				else
					PutError('Sorry, but you must be thresholding or density slicing to use Make Binary.');
			end;
	end;


	function FindMedian (var a: SortArray): integer;
  {Finds the 5th largest of 9 values}
{$IFC PowerPC}
		var
			i, j, mj, max: integer;
	begin
		for i := 1 to 4 do begin
				max := 0;
				mj := 1;
				for j := 1 to 9 do
					if a[j] > max then begin
							max := a[j];
							mj := j;
						end;
				a[mj] := 0;
			end;
		max := 0;
		for j := 1 to 9 do
			if a[j] > max then
				max := a[j];
		FindMedian := max;
	end;
	
{$ELSEC}
   {In-line code contributed by Edward J. Huff(huff@mcclbo.med.nyu.edu).}
   {Assember source with comments and a test program are available by anonymous}
   {ftp from zippy.nimh.nih.gov, in the /pub/nih-image/documents directory.}
	inline
		$205F, $48E7, $1F00, $4C98, $00FF, $B041, $6502, $C340,{}
		$B443, $6502, $C742, $B243, $6504, $C540, $C741, $B845,{}
		$6502, $CB44, $BC47, $6502, $CF46, $BA47, $6504, $CD44,{}
		$CF45, $B245, $6508, $CF43, $CD42, $CB41, $C940, $3E10,{}
		$BC47, $6502, $CF46, $BA47, $6504, $CD44, $CF45, $B245,{}
		$6508, $CF43, $CD42, $CB41, $C940, $B246, $6534, $B242,{}
		$6514, $B244, $6504, $3001, $6062, $B644, $6504, $3004,{}
		$605A, $3003, $6056, $B444, $650C, $B445, $6504, $3005,{}
		$604A, $3002, $6046, $B644, $6504, $3004, $603E, $3003,{}
		$603A, $B645, $6504, $C942, $CB43, $B846, $651C, $B644,{}
		$650C, $B444, $6504, $3002, $6022, $3004, $601E, $B646,{}
		$6504, $3003, $6016, $3006, $6012, $B646, $6508, $B446,{}
		$65F4, $3002, $6006, $B644, $65E0, $3003, $4CDF, $00F8,{}
		$3E80;
{$ENDC}



	procedure PhotoMode;
{Erases the screen to the background color and then redraws}
{the contents of the active image window . }
		var
			tPort: GrafPtr;
			event: EventRecord;
			WinRect: rect;
			SaveVisRgn: rgnHandle;
	begin
		with info^ do begin
				KillRoi;
				if OptionKeyWasDown then begin {Move window up to top of screen.}
						GetWindowRect(wptr, WinRect);
						MoveWindow(wptr, WinRect.left, 0, false);
					end;
				with wptr^ do begin
						SaveVisRgn := visRgn;
						visRgn := NewRgn;
						RectRgn(visRgn, qd.ScreenBits.Bounds);
					end;
				FlushEvents(EveryEvent, 0);
				GetPort(tPort);
				EraseScreen;
				UpdatePicWindow;
				repeat
				until button;
				with wptr^ do begin
						DisposeRgn(visRgn);
						visRgn := SaveVisRgn;
					end;
				RestoreScreen;
				SetPort(tPort);
				FlushEvents(EveryEvent, 0);
				if OptionKeyWasDown then begin
						MoveWindow(wptr, WinRect.left, WinRect.top, false);
					end;
			end;
	end;


	function AllSameSize: boolean;
{Returns true if all currently open Images have the same dimensions.}
		var
			i: integer;
			SameSize: Boolean;
			TempInfo: InfoPtr;
	begin
		if nPics = 0 then begin
				AllSameSize := false;
				exit(AllSameSize);
			end;
		SameSize := true;
		for i := 1 to nPics do begin
				TempInfo := pointer(WindowPeek(PicWindow[i])^.RefCon);
				SameSize := SameSize and EqualRect(Info^.PicRect, TempInfo^.PicRect);
			end;
		AllSameSize := SameSize;
	end;


	procedure EnhanceContrast;
		var
			AutoSelectAll: boolean;
			min, max, i, threshold: integer;
			found, SaveRedirectFlag: boolean;
			sum: LongInt;
	begin
		with info^ do
			if LUTMode = ColorLUT then begin
					PutError('Sorry, but you can not contrast enhance true color images.');
					exit(EnhanceContrast)
				end;
		if NotInBounds or (ClipBuf = nil) then
			exit(EnhanceContrast);
		StopDigitizing;
		AutoSelectAll := not Info^.RoiShowing;
		if AutoSelectAll then
			SelectAll(false);
		SaveRedirectFlag := RedirectSampling;
		RedirectSampling := false;
		if info^.RoiType = RectRoi then
			GetRectHistogram
		else
			GetHistogram;
		RedirectSampling := SaveRedirectFlag;
		sum := 0;
		for i := 0 to 255 do
			sum := sum + histogram[i];
		threshold := sum div 5000;
		i := -1;
		repeat
			i := i + 1;
			found := histogram[i] > threshold;
		until found or (i = 255);
		min := i;
		i := 256;
		repeat
			i := i - 1;
			found := histogram[i] > threshold;
		until found or (i = 0);
		max := i;
		if max > min then
			with info^ do begin
					SetupLutUndo;
					if isGrayScaleLUT then
						LUTMode := grayscale;
					ColorStart := min;
					ColorEnd := max;
					DrawMap;
					UpdateLUT;
					changes := true;
					IdentityFunction := false;
				end;
		if AutoSelectAll then
			KillRoi;
		if ScreenDepth<>8 then UpdatePicWindow;
	end;


	procedure EqualizeHistogram;
		var
			AutoSelectAll, SaveRedirectFlag: boolean;
			i, sum, v: integer;
			isum: LongInt;
			ScaleFactor: extended;
	begin
		with info^ do
			if (LUTMode <> GrayScale) and (LutMode <> CustomGrayscale) then begin
					PutError('Sorry, but you can only do histogram equalization on grayscale images.');
					exit(EqualizeHistogram)
				end;
		if NotInBounds or (ClipBuf = nil) then
			exit(EqualizeHistogram);
		StopDigitizing;
		AutoSelectAll := not Info^.RoiShowing;
		if AutoSelectAll then
			SelectAll(false);
		SaveRedirectFlag := RedirectSampling;
		RedirectSampling := false;
		if info^.RoiType = RectRoi then
			GetRectHistogram
		else
			GetHistogram;
		RedirectSampling := SaveRedirectFlag;
		FindThresholdingMode;
		ComputeResults;
		isum := 0;
		for i := 0 to 255 do
			isum := isum + histogram[i];
		ScaleFactor := 255.0 / isum;
		sum := 0;
		with info^ do begin
				SetupLutUndo;
				for i := 255 downto 0 do
					with cTable[i].rgb do begin
							sum := round(sum + histogram[i] * ScaleFactor);
							if sum > 255 then
								sum := 255;
							v := sum * 256;
							red := v;
							green := v;
							blue := v;
						end;
				LoadLUT(cTable);
				LUTMode := CustomGrayscale;
				SetupPseudocolor;
				changes := true;
				DrawMap;
				IdentityFunction := false;
			end; {with info}
		if AutoSelectAll then
			KillRoi;
		if ScreenDepth<>8 then UpdatePicWindow;
	end;


	procedure GetKernel (var kernel: ktype; var n: integer; var name: str255; RefNum: integer);
		var
			rLine: RealLine;
			i, count, nValues, nRows: integer;
	begin
		count := 0;
		nRows := 0;
		InitTextInput(name, RefNum);
		while not TextEof and (nRows <= 63) do begin
				GetLineFromText(rLine, nValues);
				if count <> 0 then
					nRows := nRows + 1;
				if nRows = 1 then
					n := nValues;
				for i := 1 to nValues do begin
						count := count + 1;
						kernel[count - 1] := round(rLine[i]);
					end;
			end;
		if count <> (n * n) then
			n := 0;
	end;


	procedure DoOnePixel (nLess1, BytesPerLine: integer; corner: LongInt; var sum: LongInt; var kernel: ktype);
{$IFC PowerPC}
		 var
			row, column, k: integer;
			pp: ptr;
	begin
		k := 0;
		sum := 0;
		for row := 0 to nless1 do begin
				corner := corner + BytesPerLine;
				pp := ptr(corner);
				for column := 0 to nless1 do begin
						sum := sum + band(pp^,255) * kernel[k];
						k := k + 1;
						pp := ptr(ord(pp) + 1);
					end;
			end;
	end;
{$ELSEC}

{a0=^corner/^sum}
{a1=^kernel}
{a2=^pixels}

{d0=n-1}
{d1=BytesPerLine}
{d2=sum}
{d3=n-1(outer loop)}
{d4=n-1(inner loop)}
{d5=temp}

inline
	$4E56, $0000, {  link	a6,#0}
	$48E7, $FCE0,  {  movem.l	a0-a2/d0-d5,-(sp)}
	$4280,              {  clr.l	d0}
	$302E, $0012, {  move.w	18(a6),d0}
	$4281,              {  clr.l	d1}
	$322E, $0010, {  move.w	16(a6),d1}
	$206E, $000C, {  movea.l	12(a6),a0}
	$226E, $0004, {  movea.l	4(a6),a1}

	$4282,             {  clr.l	d2}
	$2600,             {  move.l	d0,d3}

	$D1C1,             {A adda.l	d1,a0}
	$2448,            {  move.l	a0,a2}
	$2800,            {  move.l	d0,d4}
	$4285,            {B clr.l	d5                   (2)}
	$1A1A,             {  move.b	(a2)+,d5    (6) }
	$CBD9,             {  muls	(a1)+,d5     (29!)}
	$D485,             {  add.l	d5,d2          (2)}
	$51CC, $FFF6, {  dbra	d4,B                (6)}
	$51CB, $FFEC, {  dbra	d3,A}

	$206E, $0008, {  move.l	8(a6),a0}
	$2082,              {  move.l	d2,(a0)}
	$4CDF, $073F, {  movem.l	(sp)+,a0-a2/d0-d5}
	$4E5E,              {  unlk	a6}
	$DEFC, $0010; {  add.w	#16,sp}
{$ENDC}



procedure DoConvolution (var kernel: ktype; n: integer);
	const
		skip = 7;
	var
		row, width, column, error: integer;
		margin, i, nless1: integer;
		frame, MaskRect, tRect: rect;
		AutoSelectAll, ScalingNeeded: boolean;
		SrcCenter, DstCenter, sum, max, offset, wsum, cscale, StartTicks: LongInt;
		value, MinResult, MaxResult: LongInt;
		p: ptr;
		str, str2: str255;
		ScaleFactor: extended;
		SaveRow:integer;
		NextUpdate: LongInt;
begin
	if NotinBounds or NotRectangular then
		exit(DoConvolution);
	StopDigitizing;
	AutoSelectAll := not Info^.RoiShowing;
	if AutoSelectAll then
		SelectAll(false);
	SetupUndoFromClip;
	WhatToUndo := UndoFilter;
	frame := info^.RoiRect;
	with frame, Info^ do begin
			if ((LutMode = GrayScale) or (LutMode = CustomGrayscale)) and (not IdentityFunction) then
				ApplyLookupTable;
			changes := true;
			margin := n div 2;
			if left < margin then
				left := left + margin;
			if right > (PicRect.right - margin) then
				right := right - margin;
			if top < margin then
				top := top + margin;
			if bottom > (PicRect.bottom - margin) then
				bottom := bottom - margin;
			SetPort(wptr);
			PenNormal;
			PenPat(AntPattern[PatIndex]);
			tRect := frame;
			OffscreenToScreenRect(tRect);
			FrameRect(tRect);
			width := right - left;
			max := n * n - 1;
			wsum := 0;
			for i := 0 to max do
				wsum := wsum + kernel[i];
			NumToString(n, str);
			NumToString(wsum, str2);
			InfoMessage := Concat(str, ' x ', str, ' kernel', crStr, 'sum = ', str2, crStr, crStr, CmdPeriodToStop);
			ShowInfo;
			if wsum <> 0 then
				cscale := wsum
			else
				cscale := 1;
			offset := -(n div 2) * BytesPerRow - BytesPerRow - n div 2;
			nless1 := n - 1;
			StartTicks := TickCount;
			str := '';
			SaveRow:=top;
			NextUpdate:=TickCount+6; {Update screen 10 times per second}
			if ScaleConvolutions then begin
					MinResult := MaxLongInt;
					MaxResult := -MaxLongInt;
					row := top;
					while row < bottom do begin
							SrcCenter := ord4(ClipBufInfo^.PicBaseAddr) + row * BytesPerRow + left;
							column := left;
							while column < (left + width) do begin
									DoOnePixel(nless1, BytesPerRow, SrcCenter + offset, sum, kernel);
									value := sum div cscale;
									if value < MinResult then
										MinResult := value;
									if value > MaxResult then
										MaxResult := value;
									SrcCenter := SrcCenter + skip;
									column := column + skip;
								end; {while column}
							row := row + skip;
						end; {while row...}
					ScalingNeeded := (MinResult < 0) or (MaxResult > 255);
					if ScalingNeeded then
						ScaleFactor := 253.0 / (MaxResult - MinResult)
					else
						ScaleFactor := 1.0;
					RealToString(ScaleFactor, 1, 4, str);
					str := concat('min=', long2str(MinResult), crStr, 'max=', long2str(MaxResult), crStr, 'scale factor= ', str);
					for row := top to bottom - 1 do begin
							SrcCenter := ord4(ClipBufInfo^.PicBaseAddr) + row * BytesPerRow + left;
							DstCenter := ord4(PicBaseAddr) + row * BytesPerRow + left;
							for column := left to left + width - 1 do begin
									DoOnePixel(nless1, BytesPerRow, SrcCenter + offset, sum, kernel);
									value := sum div cscale;
									if ScalingNeeded then begin
											if value < MinResult then
												value := MinResult;
											if value > MaxResult then
												value := MaxResult;
											value := round((value - MinResult) * ScaleFactor + 1);
										end;
									p := ptr(DstCenter);
									p^ := BAND(value, 255);
									SrcCenter := SrcCenter + 1;
									DstCenter := DstCenter + 1;
								end; {for column:=}
							if TickCount>=NextUpdate then begin
								SetRect(MaskRect, left, SaveRow, right, row + 1);
								UpdateScreen(MaskRect);
								SaveRow:=row+1;
								NextUpdate:=TickCount+6;
							end;
							if CommandPeriod then begin
									UpdatePicWindow;
									beep;
									exit(DoConvolution)
								end;
						end; {for row:=...}
				end  {Scale Convolutions}
			else
				for row := top to bottom - 1 do begin
						SrcCenter := ord4(ClipBufInfo^.PicBaseAddr) + row * BytesPerRow + left;
						DstCenter := ord4(PicBaseAddr) + row * BytesPerRow + left;
						for column := left to left + width - 1 do begin
								DoOnePixel(nless1, BytesPerRow, SrcCenter + offset, sum, kernel);
								value := sum div cscale;
								if value < MinResult then
									MinResult := value;
								if value > MaxResult then
									MaxResult := value;
								if value > 255 then
									value := 255;
								if value < 0 then
									value := 0;
								p := ptr(DstCenter);
								p^ := BAND(value, 255);
								SrcCenter := SrcCenter + 1;
								DstCenter := DstCenter + 1;
							end; {for column:=}
							if TickCount>=NextUpdate then begin
								SetRect(MaskRect, left, SaveRow, right, row + 1);
								UpdateScreen(MaskRect);
								SaveRow:=row+1;
								NextUpdate:=TickCount+6;
							end;
						if CommandPeriod then begin
								UpdatePicWindow;
								beep;
								exit(DoConvolution)
							end;
					end; {for row:=...}
			SetRect(MaskRect, left, SaveRow, right, bottom);
			UpdateScreen(MaskRect);
			ShowTime(StartTicks, frame, str);
		end; {with}
	SetupRoiRect;
	if AutoSelectAll then
		KillRoi;
end;


procedure Convolve (name: str255; RefNum: integer);
	var
		kernel: ktype;
		n, count: integer;
begin
	if noUndo then exit(Convolve);
	if name = '' then begin
			RefNum := 0;
			if not GetTextFile(name, RefNum) then
				exit(convolve)
			else
				KernelsRefNum := RefNum;
		end;
	DisableDensitySlice;
	GetKernel(kernel, n, name, RefNum);
	count := n * n;
	UpdatePicWindow;
	if (n >= 3) and (n <= 63) then
		DoConvolution(kernel, n)
	else
		PutError('Kernels must be n x n square matrices with 3 <= n <= 63.');
end;


procedure ConvolveUsingText;
	var
		f: integer;
		err: OSErr;
		count: LongInt;
begin
	if noUndo then exit(ConvolveUsingText);
	err := fsdelete('TempKernel', SystemRefNum);
	err := create('TempKernel', SystemRefNum, 'imag', 'TEXT');
	if err = NoErr then
		err := fsopen('TempKernel', SystemRefNum, f);
	if err <> NoErr then begin
			PutError('Unable to open temporary file.');
			exit(ConvolveUsingText);
		end;
	if TextInfo <> nil then
		with TextInfo^ do begin
				count := TextTE^^.TELength;
				err := fswrite(f, count, TextTE^^.hText^);
				err := fsclose(f);
				Convolve('TempKernel', SystemRefNum);
				err := fsdelete('TempKernel', SystemRefNum);
			end;
end;


procedure ApplyTableToLine (data: ptr; var table: LookupTable; width: LongInt);
{$IFC PowerPC}
	var
		line: LinePtr;
		i: integer;
begin
	line := LinePtr(data);
	for i := 0 to width - 1 do
		Line^[i] := table[Line^[i]];
end;
{$ELSEC}

{a0 = data}
{a1 = lookup table}
{d0 = width }
{d1 = pixel value}
inline
	$4E56, $0000, {  link a6,#0}
	$48E7, $C0C0, {  movem.l a0-a1/d0-d1,-(sp)}
	$206E, $000C, {  move.l 12(a6),a0}
	$226E, $0008, {  move.l 8(a6),a1}
	$202E, $0004, {  move.l 4(a6),d0}
	$5380,       {  subq.l #1,d0}
	$4281,       {  clr.l d1}
	$1210,       {L move.b (a0),d1}
	$10F1, $1000, {  move.b 0(a1,d1.w),(a0)+}
	$51C8, $FFF8, {  dbra d0,L}
	$4CDF, $0303, {  movem.l (sp)+,a0-a1/d0-d1}
	$4E5E,       {  unlk a6}
	$DEFC, $000C; {  add.w #12,sp}
{$ENDC}



function NewSurfacePlot: boolean;
	const
		WidthID = 5;
		HeightID = 6;
		TitleID = 8;
		WireframeID = 11;
		GrayscaleID = 12;
		DefaultName = 'Surface Plot';
	var
		mylog: DialogPtr;
		item: integer;
		SaveWidth, SaveHeight: integer;
		name: str255;
begin
	name := DefaultName;
	if not macro and not OptionKeyWasDown then begin
			InitCursor;
			SaveWidth := NewPicWidth;
			SaveHeight := NewPicHeight;
			mylog := GetNewDialog(190, nil, pointer(-1));
			SetDNum(MyLog, WidthID, NewPicWidth);
			SelectdialogItemText(MyLog, WidthID, 0, 32767);
			SetDNum(MyLog, HeightID, NewPicHeight);
			SetDString(MyLog, TitleID, DefaultName);
			SetDlogItem(MyLog, WireframeID, ord(WireframeSurfacePlots));
			SetDlogItem(MyLog, GrayscaleID, ord(GrayscaleSurfacePlots));
			OutlineButton(MyLog, ok, 16);
			repeat
				ModalDialog(nil, item);
				if item = WidthID then begin
						NewPicWidth := GetDNum(MyLog, WidthID);
						if (NewPicWidth < 0) or (NewPicWidth > MaxPicSize) then begin
								NewPicWidth := SaveWidth;
								SetDNum(MyLog, WidthID, NewPicWidth);
							end;
					end;
				if item = HeightID then begin
						NewPicHeight := GetDNum(MyLog, HeightID);
						if (NewPicHeight < 0) or (NewPicHeight > MaxPicSize) then begin
								NewPicHeight := SaveHeight;
								SetDNum(MyLog, HeightID, NewPicHeight);
							end;
					end;
				if item = WireframeID then begin
						WireframeSurfacePlots := not WireframeSurfacePlots;
						if not WireframeSurfacePlots then
							GrayscaleSurfacePlots := true;
						SetDlogItem(MyLog, WireframeID, ord(WireframeSurfacePlots));
						SetDlogItem(MyLog, GrayscaleID, ord(GrayscaleSurfacePlots));
					end;
				if item = GrayscaleID then begin
						GrayscaleSurfacePlots := not GrayscaleSurfacePlots;
						if not GrayscaleSurfacePlots then
							WireframeSurfacePlots := true;
						SetDlogItem(MyLog, WireframeID, ord(WireframeSurfacePlots));
						SetDlogItem(MyLog, GrayscaleID, ord(GrayscaleSurfacePlots));
					end;
			until (item = ok) or (item = cancel);
			if item = ok then
				name := GetDString(MyLog, TitleID);
			DisposeDialog(mylog);
			if NewPicWidth < 32 then
				NewPicWidth := 32;
			if NewPicHeight < 16 then
				NewPicHeight := 16;
			if item = cancel then begin
					NewPicWidth := SaveWidth;
					NewPicHeight := SaveHeight;
					NewSurfacePlot := false;
					exit(NewSurfacePlot);
				end;
		end; {if not macro}
	if not GrayscaleSurfacePlots then
		SetBackgroundColor(WhiteIndex);
	NewSurfacePlot := NewPicWindow(name, NewPicWidth, NewPicHeight);
	if GrayscaleSurfacePlots then
		SetBackgroundColor(WhiteIndex);
end;


procedure PlotSurface;
      {The code for grayscale/color surface plots was contributed by}
      {Norbert Vischer (norbert@mc.bio.uva.nl). Instead of erasing and framing}
      {a polygon, it  draws a section pattern into a buffer. The section pattern}
      {depends on calibration and LUT. 'CopyBits' is used to transfer the }
      {unframed , colored polygon into the surface plot window.}
	var
		hend, vend, h, v, DataWidth, DataHeight, i: LongInt;
		htemp, vtemp, ivalue: LongInt;
		skip, DataLeft, DataRight, DataTop, DataBottom: LongInt;
		hLoc, vLoc, hMin, hMax, vMin, vMax, MinIValue, MaxIValue: LongInt;
		hstart, vstart, dh, dv, hbase, vbase, vscale, nPlotLines, CalValue: extended;
		peak, MaxPeak, hinc, vinc, nLines, MinCalValue, MaxCalValue: extended;
		poly: PolyHandle;
		SaveInfo, PlotInfo: InfoPtr;
		aLine: LineType;
		MaskRect: rect;
		AutoSelectAll, ApplyLUT: boolean;
		table: LookupTable;
		StartTicks: LongInt;
		PolyRgn: RgnHandle;
		n, LineColor, pixelvalue: LongInt;
		sourcerect, destrect: rect;
		invers: boolean;
		penheight, dx, dy: LongInt;
		thisLevel, previousLevel: LongInt;
		SectionPatternInfo: Infoptr;
		FootPrintcolor, ignore: LongInt;
		SaveForeground, SaveBackground: LongInt;
		SaveGDevice: GDHandle;

	procedure FindVinc(var vstart, MaxPeak, vinc, hinc, dh, dv:extended); {ppc-bug}
	begin
		with PlotInfo^.PicRect do begin
				vstart := 5.0 + MaxPeak - dv * DataWidth;
				skip := round(DataHeight / ((bottom - vstart - 5.0) / vinc));
				if skip = 0 then
					skip := 1;
				nPlotLines := DataHeight / skip;
				vinc := (bottom - vstart - 5.0) / nPlotLines;
				vinc := vinc / 0.95;
				repeat
					vinc := vinc * 0.95;
					hinc := vinc / 2.0;
				until (5.0 + hinc * nPlotLines + dh * DataWidth) < right;
			end;
	end;

begin
	if NotRectangular or NotInBounds then
		exit(PlotSurface);
	StopDigitizing;
	DisableDensitySlice;
	SaveInfo := Info;
	SaveForeground := ForegroundIndex;
	SaveBackground := BackgroundIndex;
	if not NewSurfacePlot then begin
			KillRoi;
			exit(PlotSurface)
		end;
	PlotInfo := info;
	info := SaveInfo;
	AutoSelectAll := not Info^.RoiShowing;
	ShowWatch;
	if AutoSelectAll then
		SelectAll(true);
	if TooWide then
		exit(PlotSurface);
	with info^ do
		ApplyLUT := ((LutMode = GrayScale) or (LutMode = CustomGrayscale)) and (not IdentityFunction);
	if ApplyLUT then
		GetLookupTable(table);
	Measure;
	UndoLastMeasurement(true);
	with results do begin
			MinIValue := MinIndex;
			MaxIValue := MaxIndex;
		end;
	if ApplyLut then begin
			MinIvalue := table[MinIValue];
			MaxIvalue := table[MaxIValue];
		end;
	MinCalValue := 10e100;
	MaxCalValue := -10e100;
	for i := MinIValue to MaxIValue do begin
			ivalue := i;
			if ApplyLUT then
				ivalue := table[ivalue];
			calValue := cvalue[i];
			if calValue < MinCalValue then
				MinCalValue := calValue;
			if calValue > MaxCalValue then
				MaxCalValue := calValue;
		end;
	WhatToUndo := NothingToUndo;
	with results do
		if (maxCValue - minCValue) <> 0.0 then
			vscale := (255.0 / (maxCValue - minCValue)) * 0.5
		else
			vscale := 0.5;
	with info^.RoiRect do begin
			DataLeft := left;
			DataRight := right;
			DataTop := top;
			DataBottom := bottom;
			DataWidth := DataRight - DataLeft;
			DataHeight := DataBottom - DataTop;
		end;
	dh := (0.65 * PlotInfo^.PicRect.right) / DataWidth;
	dv := -0.4 * dh;
	hstart := 5.0;
	vinc := 2.0;
	MaxPeak := (MaxCalValue - MinCalValue) * vscale * 0.5;
	FindVinc(vstart, MaxPeak, vinc, hinc, dh, dv); {First estimate}
	MaxPeak := MaxPeak * 2.0;
	hmin := DataRight + round(MaxPeak / dv);
	if hmin < 0 then
		hmin := 0;
	vmax := DataTop + round(MaxPeak / vinc);
	if vmax > DataBottom then
		vmax := DataBottom;
	MaxPeak := 0.0;
	vloc := DataTop;
	skip := 3;
	repeat
		hloc := hmin;
		repeat
			ivalue := MyGetPixel(hloc, vloc);
			if ApplyLUT then
				ivalue := table[ivalue];
			calValue := cvalue[ivalue];
			peak := (calValue - MinCalValue) * vscale + (DataRight - hloc) * dv - (vloc - DataTop) * vinc;
			if peak > MaxPeak then
				MaxPeak := peak;
			hloc := hloc + skip;
		until hloc > DataRight;
		vloc := vloc + skip;
	until vloc > vmax;
	FindVinc(vstart, MaxPeak, vinc, hinc, dh, dv);
	v := DataTop;
	SaveGDevice := GetGDevice;
	SetGDevice(osGDevice);
	SetPort(GrafPtr(PlotInfo^.osPort));
	if GrayscaleSurfacePlots then begin
			if not NewPicWindow('Section Pattern', NewPicWidth, NewPicHeight) then begin
					SetGDevice(SaveGDevice);
					exit(PlotSurface);
				end;
			SectionPatternInfo := Info;
			sourceRect := PlotInfo^.osPort^.portRect;
			SetPort(GrafPtr(SectionPatternInfo^.osPort));
			pmForeColor(BlackIndex);
			pmBackColor(WhiteIndex);
			PenNormal;
			previousLevel := sourcerect.bottom + 10; {add a safety band to the bottom}
			invers := cvalue[0] > cvalue[255];
			dx := sourcerect.right * 3 div 2;{>0.65}
			dy := dx * 2 div 5;{* 0.4}
			FootPrintcolor := -1;
			for n := 0 to 255 do begin{build inclined scale from bottom to top}
					if invers then
						pixelvalue := 255 - n
					else
						pixelvalue := n;
					if ApplyLUT then
						LineColor := table[pixelvalue]
					else
						LineColor := pixelvalue;
					thisLevel := round(sourcerect.bottom - vscale * (cvalue[LineColor] - MinCalValue));
					penheight := previousLevel - thisLevel;
					if penheight > 0 then begin{only if color was not dropped}
							MoveTo(0, thisLevel);
							pmForeColor(pixelvalue);
							if FootPrintcolor = -1 then{catch the lowest color}
								FootPrintcolor := pixelvalue;
							pensize(1, penheight);
							line(dx, -dy);
							previousLevel := thisLevel;
						end;
				end;
			move(0, -10);
			pensize(1, 10);
			line(-dx, dy);{add a safety band to the top}
			PolyRgn := NewRgn;
		end; {if GrayscaleSurfacePlots}
	info := PlotInfo;
	SetGDevice(SaveGDevice);
	SelectWindow(PlotInfo^.wptr);
	SetGDevice(osGDevice);
	SetPort(GrafPtr(PlotInfo^.osPort));
	pmForeColor(BlackIndex);
	pmBackColor(WhiteIndex);
	PenNormal;
	UpdatePicWindow;
	StartTicks := TickCount;
	repeat
		hmax := 0;
		vmin := 9999;
		poly := OpenPoly;
		hbase := hstart;
		vbase := vstart;
		Info := SaveInfo;
		GetLine(DataLeft, v, DataWidth, aLine);
		info := PlotInfo;
		if ApplyLUT then
			ApplyTableToLine(@aLine, table, DataWidth);
		MoveTo(round(hbase), round(vbase - vscale * (cvalue[aLine[0]] - MinCalValue)));
		for i := 0 to DataWidth - 1 do begin
				hbase := hbase + dh;
				vbase := vbase + dv;
				hLoc := round(hbase);
				vLoc := round(vbase - vscale * (cvalue[aLine[i]] - MinCalValue));
				LineTo(hloc, vloc);
				if hloc > hmax then
					hmax := hloc;
				if vloc < vmin then
					vmin := vloc;
			end;
		LineTo(round(hbase), round(vbase));
		LineTo(round(hstart), round(vstart));
		LineTo(round(hstart), round(vstart - vscale * (cvalue[aLine[0]] - MinCalValue)));
		hmin := round(hstart);
		vmax := round(vstart);
		ClosePoly;
		if GrayscaleSurfacePlots then begin
				destrect := poly^^.polybbox;
                {now adjust size and position of sourcerect to get the right colors into the section : }
				sourcerect.right := destrect.right - destrect.left;
				sourcerect.top := sourcerect.bottom - (destrect.bottom - destrect.top);
				OpenRgn;
				FramePoly(poly);
				CloseRgn(PolyRgn);
				CopyBits(BitMapHandle(SectionPatternInfo^.osPort^.portPixMap)^^, BitMapHandle(info^.osPort^.portPixMap)^^, sourcerect, destrect, srcCopy, PolyRgn);
				if WireframeSurfacePlots then
					FramePoly(poly);
			end    {if GrayscaleSurfacePlots}
		else begin
				ErasePoly(poly);
				FramePoly(poly);
			end;
		KillPoly(poly);
		SetRect(MaskRect, hmin, vmin, hmax, vmax);
		UpdateScreen(MaskRect);
		hstart := hstart + hinc;
		vstart := vstart + vinc;
		v := v + skip;
	until (v >= DataBottom) or CommandPeriod;
	ShowTime(StartTicks, SaveInfo^.RoiRect, '');
	if CommandPeriod then
		beep;
	SetForegroundColor(SaveForeground);
	SetBackgroundColor(SaveBackground);
	info^.changes := true;
	if GrayscaleSurfacePlots then begin
			disposergn(PolyRgn);
			ignore := CloseAWindow(SectionPatternInfo^.wptr);
			info := PlotInfo;
		end;
	SetGDevice(SaveGDevice);
end;


{$POP}


function isqrt(x:LongInt):LongInt;
{The algorithm used by this integer square root function is described
in the document "square-root-code.txt" in the /pub/nih-image/documents
directory on zippy.nih.gov.} 
var
	r,nr,m:LongInt;
begin
    r:=0;
    m:=$40000000;
    repeat
    	nr := r + m;
        if (nr <= x) then begin
            x := x - nr;
            r := nr + m;
         end;
        r := bsr(r,1);
        m := bsr(m,2);
    until m = 0;
    if x > r then r:=r+1;
    isqrt:=r;
end;


procedure Filter (ftype: FilterType; pass: integer; var table: FateTable);
	var
		row, width, r1, r2, r3, c, value, error, sum, center: integer;
		tmp, SaveRow: integer;
		t1, t2, t3, t4: integer;
		MaskRect, frame, trect: rect;
		Filler1: integer;
		L1: LineType;
		Filler2: integer;
		L2: LineType;
		Filler3: integer;
		L3: LineType;
		Filler4: integer;
		result: LineType;
		a: SortArray;
		AutoSelectAll, UseMask, ProcessEdgePixels: boolean;
		L, T, R, B, index, code, FirstRow, LastRow: integer;
		StartTicks: LongInt;
		min, max, i, SaveBackground: integer;
		NextUpdate,tmp1,tmp2,tmp3:LongInt;
begin
	if NotinBounds then
		exit(Filter);
	StopDigitizing;
	AutoSelectAll := not Info^.RoiShowing;
	if AutoSelectAll then
		with info^ do begin
				SelectAll(false);
				SetPort(wptr);
				PenNormal;
				PenPat(AntPattern[PatIndex]);
				FrameRect(wrect);
			end;
	if TooWide then
		exit(Filter);
	ShowWatch;
	if info^.RoiType <> RectRoi then
		UseMask := SetupMask
	else
		UseMask := false;
	if pass = 0 then begin
			SetupUndoFromClip;
			ShowMessage(CmdPeriodToStop);
			WhatToUndo := UndoFilter;
		end;
	frame := info^.RoiRect;
	StartTicks := TickCount;
	SaveBackground := BackgroundIndex;
	if ftype = MinRankFilter then
		BackgroundIndex := BlackIndex
	else
		BackgroundIndex := WhiteIndex;
	ProcessEdgePixels := ftype in [Erosion, Dilation, OutlineFilter, Skeletonize, Dither, MinRankFilter, MaxRankFilter];
	with frame, Info^ do begin
			changes := true;
			RoiShowing := false;
			width := right - left;
			if ProcessEdgePixels then begin
					FirstRow := top;
					LastRow := bottom - 1;
					L1[-1] := BackgroundIndex; {Out of bounds stores are intentional}
					L2[-1] := BackgroundIndex;
					L3[-1] := BackgroundIndex;
					if width < MaxLine then begin
							L1[width] := BackgroundIndex;
							L2[width] := BackgroundIndex;
							L3[width] := BackgroundIndex;
						end
				end
			else begin
					FirstRow := top + 1;
					LastRow := bottom - 2;
				end;
			GetLine(left, FirstRow - 1, width, L2);
			GetLine(left, FirstRow, width, L3);
			SaveRow := RoiRect.top;
			NextUpdate:=TickCount+6; {Update screen 10 times per second}
			for row := FirstRow to LastRow do begin
             {Move Convolution Window Down}
					BlockMove(@L2, @L1, width);
					BlockMove(@L3, @L2, width);
					GetLine(left, row + 1, width, L3);
                  {Process One Row}
					case ftype of
						FindEdges: 
							for c := 1 to width - 2 do begin
									tmp1 := L1[c - 1] + L1[c]*2 + L1[c + 1] - L3[c - 1] - L3[c]*2 - L3[c + 1];
									tmp2 := L1[c + 1] + L2[c + 1]*2 + L3[c + 1] - L1[c - 1] - L2[c - 1]*2 - L3[c - 1];
									tmp3:=isqrt(tmp1*tmp1+tmp2*tmp2);
									if tmp3>255 then tmp3:=255;
									result[c]:=tmp3;
								end;
						EdgeDetect: 
							for c := 1 to width - 2 do begin
									t1 := L1[c - 1] + L1[c] + L1[c + 1] - L3[c - 1] - L3[c] - L3[c + 1];
									t1 := abs(t1);
									t2 := L1[c + 1] + L2[c + 1] + L3[c + 1] - L1[c - 1] - L2[c - 1] - L3[c - 1];
									t2 := abs(t2);
									if t1 > t2 then
										tmp := t1
									else
										tmp := t2;
									if OptionKeyWasDown then begin
											if tmp > 255 then
												tmp := 255;
											if tmp < 0 then
												tmp := 0;
										end
									else if tmp > 35 then
										tmp := 255
									else
										tmp := 0;
									result[c] := tmp;
								end;
						ReduceNoise:  {median filter}
							for c := 0 to width - 1 do begin
									a[1] := L1[c - 1];
									a[2] := L1[c];
									a[3] := L1[c + 1];
									a[4] := L2[c - 1];
									a[5] := L2[c];
									a[6] := L2[c + 1];
									a[7] := L3[c - 1];
									a[8] := L3[c];
									a[9] := L3[c + 1];
									result[c] := FindMedian(a);
								end;
						MinRankFilter: 
							for c := 0 to width - 1 do begin
									min := 255;
									value := L1[c - 1];
									if value < min then
										min := value;
									value := L1[c];
									if value < min then
										min := value;
									value := L1[c + 1];
									if value < min then
										min := value;
									value := L2[c - 1];
									if value < min then
										min := value;
									value := L2[c];
									if value < min then
										min := value;
									value := L2[c + 1];
									if value < min then
										min := value;
									value := L3[c - 1];
									if value < min then
										min := value;
									value := L3[c];
									if value < min then
										min := value;
									value := L3[c + 1];
									if value < min then
										min := value;
									result[c] := min;
								end;
						MaxRankFilter: 
							for c := 0 to width - 1 do begin
									max := 0;
									value := L1[c - 1];
									if value > max then
										max := value;
									value := L1[c];
									if value > max then
										max := value;
									value := L1[c + 1];
									if value > max then
										max := value;
									value := L2[c - 1];
									if value > max then
										max := value;
									value := L2[c];
									if value > max then
										max := value;
									value := L2[c + 1];
									if value > max then
										max := value;
									value := L3[c - 1];
									if value > max then
										max := value;
									value := L3[c];
									if value > max then
										max := value;
									value := L3[c + 1];
									if value > max then
										max := value;
									result[c] := max;
								end;
						Dither:  {Floyd-Steinberg Algorithm}
							for c := 0 to width - 1 do begin
									value := L2[c];
									if value < 128 then begin
											result[c] := 0;
											error := -value;
										end
									else begin
											result[c] := 255;
											error := 255 - value
										end;
									tmp := L2[c + 1];              {A}
									tmp := tmp - (7 * error) div 16;
									if tmp < 0 then
										tmp := 0;
									if tmp > 255 then
										tmp := 255;
									L2[c + 1] := tmp;
									tmp := L3[c + 1];              {B}
									tmp := tmp - error div 16;
									if tmp < 0 then
										tmp := 0;
									if tmp > 255 then
										tmp := 255;
									L3[c + 1] := tmp;
									tmp := L3[c];              {C}
									tmp := tmp - (5 * error) div 16;
									if tmp < 0 then
										tmp := 0;
									if tmp > 255 then
										tmp := 255;
									L3[c] := tmp;
									tmp := L3[C - 1];                {D}
									tmp := tmp - (3 * error) div 16;
									if tmp < 0 then
										tmp := 0;
									if tmp > 255 then
										tmp := 255;
									L3[C - 1] := tmp;
								end;
						UnweightedAvg: 
							for c := 1 to width - 2 do begin
									tmp := (L1[C - 1] + L1[c] + L1[c + 1] + L2[c - 1] + L2[c] + L2[c + 1] + L3[c - 1] + L3[c] + L3[c + 1]) div 9;
									if tmp > 255 then
										tmp := 255;
									if tmp < 0 then
										tmp := 0;
									result[c] := tmp;
								end;
						WeightedAvg: 
							for c := 1 to width - 2 do begin
									tmp := (L1[c - 1] + L1[c] + L1[c + 1] + L2[c - 1] + L2[c] * 4 + L2[c + 1] + L3[c - 1] + L3[c] + L3[c + 1]) div 12;
									if tmp > 255 then
										tmp := 255;
									if tmp < 0 then
										tmp := 0;
									result[c] := tmp;
								end;
						fsharpen: 
							for c := 1 to width - 2 do begin
									if OptionKeyWasDown then
										tmp := L2[c] * 9 - L1[c - 1] - L1[c] - L1[c + 1] - L2[c - 1] - L2[c + 1] - L3[c - 1] - L3[c] - L3[c + 1]
									else begin
											tmp := L2[c] * 12 - L1[c - 1] - L1[c] - L1[c + 1] - L2[c - 1] - L2[c + 1] - L3[c - 1] - L3[c] - L3[c + 1];
											tmp := tmp div 4;
										end;
									if tmp > 255 then
										tmp := 255;
									if tmp < 0 then
										tmp := 0;
									result[c] := tmp;
								end;
						SharpenMore: 
							for c := 1 to width - 2 do begin
										tmp := L2[c] * 9 - L1[c - 1] - L1[c] - L1[c + 1] - L2[c - 1] - L2[c + 1] - L3[c - 1] - L3[c] - L3[c + 1];
									if tmp > 255 then
										tmp := 255;
									if tmp < 0 then
										tmp := 0;
									result[c] := tmp;
								end;
						ShadowN: 
							for c := 1 to width - 2 do begin
									tmp:= L1[c-1] + L1[c]*2 + L1[c+1] + L2[c] - L3[c-1] - L3[c]*2 - L3[c+1];
									if tmp > 255 then
										tmp := 255;
									if tmp < 0 then
										tmp := 0;
									result[c] := tmp;
								end;
						ShadowNE: 
							for c := 1 to width - 2 do begin
									tmp:= L1[c] + L1[c+1]*2 - L2[c-1] + L2[c] + L2[c+1] - L3[c-1]*2 - L3[c];
									if tmp > 255 then
										tmp := 255;
									if tmp < 0 then
										tmp := 0;
									result[c] := tmp;
								end;
						ShadowE: 
							for c := 1 to width - 2 do begin
									tmp:= L1[c+1] -L1[c-1] - L2[c-1]*2 + L2[c] + L2[c+1]*2 - L3[c-1] + L3[c+1];
									if tmp > 255 then
										tmp := 255;
									if tmp < 0 then
										tmp := 0;
									result[c] := tmp;
								end;
						ShadowSE: 
							for c := 1 to width - 2 do begin
									tmp:= L3[c] + L3[c+1]*2 - L1[c-1]*2 - L1[c] - L2[c-1] + L2[c] + L2[c+1];
									if tmp > 255 then
										tmp := 255;
									if tmp < 0 then
										tmp := 0;
									result[c] := tmp;
								end;
						ShadowS: 
							for c := 1 to width - 2 do begin
									tmp:= L3[c-1] + L3[c]*2 + L3[c+1] - L1[c-1] - L1[c]*2 - L1[c+1] + L2[c];
									if tmp > 255 then
										tmp := 255;
									if tmp < 0 then
										tmp := 0;
									result[c] := tmp;
								end;
						ShadowSW: 
							for c := 1 to width - 2 do begin
									tmp:= L2[c-1] + L2[c] - L2[c+1] + L3[c-1]*2 + L3[c] - L1[c] - L1[c+1]*2;
									if tmp > 255 then
										tmp := 255;
									if tmp < 0 then
										tmp := 0;
									result[c] := tmp;
								end;
						ShadowW: 
							for c := 1 to width - 2 do begin
									tmp:= L1[c-1] - L1[c+1] + L2[c-1]*2 + L2[c] - L2[c+1]*2 + L3[c-1] - L3[c+1];
									if tmp > 255 then
										tmp := 255;
									if tmp < 0 then
										tmp := 0;
									result[c] := tmp;
								end;
						ShadowNW: 
							for c := 1 to width - 2 do begin
									tmp:= L1[c-1]*2 + L1[c] + L2[c-1] + L2[c] - L2[c+1] - L3[c] - L3[c+1]*2;
									if tmp > 255 then
										tmp := 255;
									if tmp < 0 then
										tmp := 0;
									result[c] := tmp;
								end;
						Erosion: 
							for c := 0 to width - 1 do begin
									center := L2[c];
									if center = BlackIndex then begin
											sum := L1[c - 1] + L1[c] + L1[c + 1] + L2[c - 1] + L2[c + 1] + L3[c - 1] + L3[c] + L3[c + 1];
											if (2040 - sum) >= BinaryThreshold then
												center := WhiteIndex;
										end;
									result[c] := center;
								end;
						Dilation: 
							for c := 0 to width - 1 do begin
									center := L2[c];
									if center = WhiteIndex then begin
											sum := L1[c - 1] + L1[c] + L1[c + 1] + L2[c - 1] + L2[c + 1] + L3[c - 1] + L3[c] + L3[c + 1];
											if sum >= BinaryThreshold then
												center := BlackIndex;
										end;
									result[c] := center;
								end;
						OutlineFilter: 
							for c := 0 to width - 1 do begin
									center := L2[c];
									if center = BlackIndex then begin
											if (L2[c - 1] = WhiteIndex) or (L1[c] = WhiteIndex) or (L2[c + 1] = WhiteIndex) or (L3[c] = WhiteIndex)
											or (L1[c-1] = WhiteIndex) or (L1[c+1] = WhiteIndex) or (L3[c-1] = WhiteIndex) or (L3[c+1] = WhiteIndex) then
												center := BlackIndex
											else
												center := WhiteIndex;
										end;
									result[c] := center;
								end;

						Skeletonize: 
							for c := 0 to width - 1 do begin
									center := L2[c];
									if center = BlackIndex then begin
											index := 0;											if L1[c - 1] = BlackIndex then
												index := bor(index, 1);
											if L1[c] = BlackIndex then
												index := bor(index, 2);
											if L1[c + 1] = BlackIndex then
												index := bor(index, 4);
											if L2[c + 1] = BlackIndex then
												index := bor(index, 8);
											if L3[c + 1] = BlackIndex then
												index := bor(index, 16);
											if L3[c] = BlackIndex then
												index := bor(index, 32);
											if L3[c - 1] = BlackIndex then
												index := bor(index, 64);
											if L2[c - 1] = BlackIndex then
												index := bor(index, 128);

											code := table[index];
											if odd(pass) then begin
													if (code = 2) or (code = 3) then begin
															center := WhiteIndex;
															PixelsRemoved := PixelsRemoved + 1;
														end;
												end
											else begin {even pass}
													if (code = 1) or (code = 3) then begin
															center := WhiteIndex;
															PixelsRemoved := PixelsRemoved + 1;
														end;
												end;
										end; {if}
									result[c] := center;
								end; {for}
					end; {case}
					if not ProcessEdgePixels then begin
							result[0] := L2[0];
							result[width - 1] := L2[width - 1];
						end;
					if UseMask then
						PutLineUsingMask(left, row, width, result)
					else
						PutLine(left, row, width, result);
					if TickCount>=NextUpdate then begin
							with RoiRect do
								SetRect(MaskRect, left, SaveRow, right, row+1);
							UpdateScreen(MaskRect);
							SaveRow:=row+1;
							if magnification > 1.0 then
								SaveRow := SaveRow - 1;
							NextUpdate:=TickCount+6;
							if CommandPeriod then begin
									UpdatePicWindow;
									beep;
									PixelsRemoved := 0;
									if AutoSelectAll then
										KillRoi;
									BackgroundIndex := SaveBackground;
									exit(filter)
								end;
						end;
				end; {for row:=...}
			trect := frame;
			InsetRect(trect, 1, 1);
			ShowTime(StartTicks, trect, '');
		end; {with}
	with frame do
		SetRect(MaskRect, left, SaveRow, right, bottom);
	UpdateScreen(MaskRect);
	SetupRoiRect;
	if AutoSelectAll then
		KillRoi;
	BackgroundIndex := SaveBackground;
end; {Filter}



procedure MakeSkeleton;
{This table-driven parallel thinning routine is based on an algorithm}
{by Zhang and Suen (CACM, March 1984, 236-239). There is}
{an entry in the table for each of the 256 possible 3x3 neighborhood}
{configurations. An entry of '1' means delete pixel on first pass, '2' means}
{delete pixel on second pass, and '3' means delete on either pass. There is a}
{routine in 'user.p' that draws all 256 neighborhoods.}
	const
		s999 = '01234567890123456789012345678901';
		s000 = '00010013003110130000000020203033';
		s032 = '00000000300000002000000020003022';
		s064 = '00000000000000000000000000000000';
		s096 = '20000000200020003000000030003020';
		s128 = '01310013000000010000000000000001';
		s160 = '31000000000000002000000000000000';
		s192 = '23130013000000010000000000000000';
		s224 = '2301000100000000330100002200200';
	var
		table: FateTable;
		s: str255;
		i, pass: integer;
begin
	s := concat(s000, s032, s064, s096, s128, s160, s192, s224);
	for i := 0 to 254 do
		table[i] := ord(s[i + 1]) - ord('0');
	table[255] := 0;
	pass := 0;
	repeat
		PixelsRemoved := 0;
		filter(skeletonize, pass, table);
		pass := pass + 1;
		if not CommandPeriod then
			filter(skeletonize, pass, table);
		pass := pass + 1;
	until (PixelsRemoved = 0) or CommandPeriod;
end;


procedure DoErosion;
	var
		i: integer;
		t: FateTable;
begin
	for i := 0 to BinaryIterations - 1 do begin
			filter(Erosion, i, t);
			if CommandPeriod then
				leave;
		end;
end;


procedure DoDilation;
	var
		i: integer;
		t: FateTable;
begin
	for i := 0 to BinaryIterations - 1 do begin
			filter(Dilation, i, t);
			if CommandPeriod then
				leave;
		end;
end;


procedure DoOpening;
	var
		i: integer;
		t: FateTable;
begin
	for i := 0 to BinaryIterations - 1 do begin
			filter(Erosion, i, t);
			if CommandPeriod then
				exit(DoOpening);
		end;
	for i := 0 to BinaryIterations - 1 do begin
			filter(Dilation, i + BinaryIterations, t);
			if CommandPeriod then
				exit(DoOpening);
		end;
end;

procedure DoClosing;
	var
		i: integer;
		t: FateTable;
begin
	for i := 0 to BinaryIterations - 1 do begin
			filter(Dilation, i, t);
			if CommandPeriod then
				exit(DoClosing);
		end;
	for i := 0 to BinaryIterations - 1 do begin
			filter(Erosion, i + BinaryIterations, t);
			if CommandPeriod then
				exit(DoClosing);
		end;
end;

procedure SetBinaryCount;
	var
		TempCount: integer;
		Canceled: boolean;
begin
	TempCount := GetInt('Neighborhood Pixel Count(1-8):', BinaryCount, Canceled);
	if Canceled then
		exit(SetBinaryCount);
	if (TempCount >= 1) and (TempCount <= 8) then begin
			BinaryCount := TempCount;
			BinaryThreshold := BinaryCount * 255
		end
	else
		beep;
end;

procedure SetIterations;
	var
		TempIterations: integer;
		Canceled: boolean;
begin
	TempIterations := GetInt('Number of Iterations:', BinaryIterations, Canceled);
	if Canceled then
		exit(SetIterations);
	if (TempIterations >= 1) and (TempIterations < 100) then
		BinaryIterations := TempIterations
	else
		beep;
end;


procedure ChangeValues (v1, v2, v3: integer);
      {Changes all pixels in the current selection with a value in the range v1 to v2 to a value of v3.}
	var
		i, value: integer;
		table: LookupTable;
begin
	for i := 0 to 255 do begin
			value := i;
			if (value >= v1) and (value <= v2) then
				value := v3;
			table[i] := value;
		end;
	ApplyTable(table);
end;


procedure DoPropagate (MenuItem: integer);
      {Copies the current Look-Up Table, spatial calibration, or density calibration to all open windows.}
	var
		TempInfo: InfoPtr;
		i: integer;

	procedure CopyLUTInfo;
	begin
		with info^ do begin
				TempInfo^.RedLUT := RedLUT;
				TempInfo^.GreenLUT := GreenLUT;
				TempInfo^.BlueLUT := BlueLUT;
				TempInfo^.ColorStart := ColorStart;
				TempInfo^.ColorEnd := ColorEnd;
				TempInfo^.nColors := nColors;
				TempInfo^.LutMode := LUTMode;
				TempInfo^.cTable := cTable;
				TempInfo^.FillColor1 := FillColor1;
				TempInfo^.FillColor2 := FillColor2;
				TempInfo^.FillColor1 := FillColor1;
				TempInfo^.SaveFill1 := SaveFill1;
				TempInfo^.SaveFill2 := SaveFill2;
			end;
	end;

	procedure CopySpatialCalibration;
		var
			SaveInfo: InfoPtr;
	begin
		with info^ do begin
				TempInfo^.xScale := xScale;
				TempInfo^.yScale := yScale;
				TempInfo^.PixelAspectRatio := PixelAspectRatio;
				TempInfo^.xUnit := xUnit;
				TempInfo^.changes := true;
				TempInfo^.SpatiallyCalibrated := SpatiallyCalibrated;
			end;
		SaveInfo := Info;
		Info := TempInfo;
		UpdateTitleBar;
		Info := SaveInfo;
	end;

	procedure CopyDensityCalibration;
		var
			SaveInfo: InfoPtr;
	begin
		with info^ do begin
				TempInfo^.ZeroClip := ZeroClip;
				TempInfo^.fit := fit;
				TempInfo^.nCoefficients := nCoefficients;
				TempInfo^.Coefficient := Coefficient;
				TempInfo^.UnitOfMeasure := UnitOfMeasure;
				TempInfo^.changes := true;
			end;
		SaveInfo := Info;
		Info := TempInfo;
		UpdateTitleBar;
		Info := SaveInfo;
	end;

begin
	for i := 1 to nPics do begin
			TempInfo := pointer(WindowPeek(PicWindow[i])^.RefCon);
			case MenuItem of
				1: 
					CopyLUTInfo;
				2: 
					CopySpatialCalibration;
				3: 
					CopyDensityCalibration;
			end; {case}
		end;
	WhatToUndo := NothingToUndo;
end;


procedure AutoThreshold;
       {Iterative thresholding technique, described originally by Ridler & Calvard in}
       {"PIcture Thresholding Using an Iterative Selection Method", IEEE transactions}
       { on Systems, Man and Cybernetics, August, 1978. }
	var
		AutoSelectAll, SaveRedirectFlag: boolean;
		index, MovingIndex, level: integer;
		tempSum1, tempSum2, tempSum3, tempSum4, result: extended;
begin
	AutoSelectAll := not info^.RoiShowing;
	if AutoSelectAll then
		SelectAll(false);
	SaveRedirectFlag := RedirectSampling;
	RedirectSampling := false;
	if info^.RoiType = RectRoi then
		GetRectHistogram
	else
		GetHistogram;
	RedirectSampling := SaveRedirectFlag;
	OptionKeyWasDown := OptionKeyDown;
	if not OptionKeyWasDown then begin
              {Default is to set to these to null so erased areas won't be included in the threshold }
			Histogram[0] := 0;
			Histogram[255] := 0;
		end;
	with Results do begin {From ComputeResults}
			MinIndex := 0;
			while (histogram[MinIndex] = 0) and (MinIndex < 255) do
				MinIndex := MinIndex + 1;
			MaxIndex := 255;
			while (histogram[MaxIndex] = 0) and (MaxIndex > 0) do
				MaxIndex := MaxIndex - 1;
			if (MinIndex >= MaxIndex) then begin
					level := 128;
					ShowMessage(concat('Threshold=', Long2Str(level)));
					EnableThresholding(level);
					exit(AutoThreshold);
				end;
			MovingIndex := MinIndex;
			repeat
				tempSum1 := 0;
				tempSum2 := 0;
				tempSum3 := 0;
				tempSum4 := 0;
				for index := MinIndex to MovingIndex do begin
						tempSum1 := tempSum1 + index * Histogram[index];
						tempSum2 := tempSum2 + Histogram[index];
					end;
				for index := (MovingIndex + 1) to MaxIndex do begin
						tempSum3 := tempSum3 + index * Histogram[index];
						tempSum4 := tempSum4 + Histogram[index];
					end;
				Result := (tempSum1 / TempSum2 / 2.0) + (tempSum3 / tempSum4 / 2.0);
				MovingIndex := MovingIndex + 1;
			until ((MovingIndex + 1) > result) or (MovingIndex > (MaxIndex - 1));
			level := Round(result);
			EnableThresholding(level);
			ShowMessage(concat('Threshold=', Long2Str(level)));
		end; {with}
end;


procedure AutoDensitySlice;
	var
		AutoSelectAll: boolean;
		sigmak1k2, sigmax, nsum: extended;
		i, j, maxk1, maxk2, temp: integer;
		musubt, omegak1, omegak2, muk1, muk2: extended;
		part1, part2, part3: extended;
		intermed1, intermed2, intermed3: extended;
begin
	ResetGrayMap;
	AutoSelectAll := not info^.RoiShowing;
	if AutoSelectAll then
		SelectAll(false);
	if info^.RoiType = RectRoi then
		GetRectHistogram
	else
		GetHistogram;
	maxk1 := 0;
	maxk2 := 0;
	musubt := 0.0;
	nsum := 0.0;
	for i := 1 to 254 do begin
			nsum := nsum + histogram[i];
		end;
	for i := 1 to 254 do begin
			musubt := musubt + (i * (histogram[i] / nsum));
		end;
	sigmak1k2 := 0.0;
	sigmax := 0.0;
	omegak1 := 0.0;
	muk1 := 0.0;
	for i := 1 to 253 do begin
			temp := i + 1;
			omegak2 := 0.0;
			muk2 := 0.0;
			omegak1 := omegak1 + (histogram[i] / nsum);
			muk1 := muk1 + (i * (histogram[i] / nsum));
			if omegak1 > 0.0 then begin
					for j := temp to 254 do begin
							omegak2 := omegak2 + (histogram[j] / nsum);
							muk2 := muk2 + (j * (histogram[j] / nsum));
							if omegak1 * omegak2 * (1.0 - omegak1 - omegak2) > 0.0 then begin
									part1 := ((omegak1 * muk2) - (omegak2 * muk1)) * ((omegak1 * muk2) - (omegak2 * muk1));
									intermed1 := omegak2 * omegak1;
									part2 := ((omegak1 * (musubt - muk2)) - (muk1 * (1 - omegak2))) * ((omegak1 * (musubt - muk2)) - (muk1 * (1 - omegak2)));
									intermed2 := omegak1 * (1 - omegak1 - omegak2);
									part3 := ((omegak2 * (musubt - muk1)) - (muk2 * (1 - omegak1))) * ((omegak2 * (musubt - muk1)) - (muk2 * (1 - omegak1)));
									intermed3 := omegak2 * (1 - omegak1 - omegak2);
									if intermed1 * intermed2 * intermed3 > 0.0 then begin
											sigmak1k2 := part1 / intermed1 + part2 / intermed2 + part3 / intermed3;
										end;
								end;
							if sigmak1k2 > sigmax then begin
									maxk1 := i;
									maxk2 := j;
									sigmax := sigmak1k2;
								end;
						end;
				end;
		end;
	SliceStart := maxk1;
	SliceEnd := maxk2;
end;

function GetScaleAndAngle: boolean;
	const
		hScaleID = 7;
		vScaleID = 8;
		AngleID = 9;
		NearestNeighborID = 10;
		BilinearID = 11;
		NewWindowID = 12;
	var
		mylog: DialogPtr;
		item, i: integer;
		vScaleUnchanged: boolean;
		str: str255;
begin
	vScaleUnchanged := true;
	InitCursor;
	mylog := GetNewDialog(50, nil, pointer(-1));
	SetDReal(MyLog, AngleID, rsAngle, 2);
	SetDReal(MyLog, hScaleID, rsHScale, 2);
	SelectdialogItemText(MyLog, hScaleID, 0, 32767);
	SetDReal(MyLog, vScaleID, rsVScale, 2);
	SetDlogItem(mylog, NewWindowID, ord(rsCreateNewWindow));
	SetDlogItem(mylog, BilinearID, ord(rsMethod = Bilinear));
	SetDlogItem(mylog, NearestNeighborID, ord(rsMethod = NearestNeighbor));
	repeat
		ModalDialog(nil, item);
		if item = AngleID then begin
				rsAngle := GetDReal(MyLog, AngleID);
				if rsAngle > 180.0 then
					rsAngle := 180.0;
				if rsAngle < -180.0 then
					rsAngle := -180.0;
			end;
		if item = hScaleID then begin
				str := GetDString(MyLog, hScaleID);
				rsHScale := StringToReal(str);
				if rsHScale = BadReal then
					rsHScale := 1.0;
				if vScaleUnchanged then begin
						rsVScale := rsHScale;
						SetDString(MyLog, vScaleID, str);
					end;
				if rsHScale < 0.05 then
					rsHScale := 0.05;
			end;
		if item = vScaleID then begin
				rsVScale := GetDReal(MyLog, vScaleID);
				if rsVScale = BadReal then
					rsVScale := 1.0;
				if rsVScale < 0.05 then
					rsVScale := 0.05;
				vScaleUnchanged := false;
			end;
		if item = NewWindowID then begin
				rsCreateNewWindow := not rsCreateNewWindow;
				SetDlogItem(mylog, NewWindowID, ord(rsCreateNewWindow));
			end;
		if (item = BilinearID) or (item = NearestNeighborID) then begin
				if item = BilinearID then
					rsMethod := Bilinear;
				if item = NearestNeighborID then
					rsMethod := NearestNeighbor;
				SetDlogItem(mylog, BilinearID, ord(rsMethod = Bilinear));
				SetDlogItem(mylog, NearestNeighborID, ord(rsMethod = NearestNeighbor));
			end;
	until (item = ok) or (item = cancel);
	DisposeDialog(mylog);
	GetScaleAndAngle := item <> cancel;
end;



procedure ScaleAndRotate;
	type
		EraseType = (Erase, DontErase);
	var
		value, DstWidth, DstHeight, hstart, vstart, hend, vend: integer;
		SaveWidth, SaveHeight: integer;
		hSrcCenter, vSrcCenter, hDstCenter, vDstCenter: integer;
		hbase, vbase, SrcWidth, SrcHeight: integer;
		SrcInfo, DstInfo, SaveInfo: InfoPtr;
		AutoSelectAll, UseNearestNeighbor, Rotate: boolean;
		MaskRect, SourceRect, DstRect: rect;
		StartTicks: LongInt;
		UseSameWindow: boolean;
		LinesPerUpdate: integer;

	procedure DoInterpolatedScaling;
    {Does interpolated scaling, but no rotation, using scaled integer arithmetic.}
		const
			CountsPerUpdate = 5;
		var
			SrcLeft, hloc, vloc, vbase, hbase, hrel: integer;
			LineCount, oldvloc, LastLine: integer;
			DstLine, SrcLine1, SrcLine2: LineType;
			MaskRect: rect;
			v, SrcTop: extended;
			h, hFraction, vFraction, UpperAverage, LowerAverage: LongInt;
			scale, scale2, hscale: LongInt;
	begin
		if (SrcWidth > MaxLine) or (DstWidth > MaxLine) then begin
			PutError(StringOf('Width must be less than ', MaxLine:1, ' pixels.'));
			exit(DoInterpolatedScaling);
		end;
		scale := 500;
		scale2 := scale * scale;
		hscale := round(rsHScale * scale);
		LastLine := SrcInfo^.PicRect.bottom - 1;
		with SourceRect do begin
				SrcLeft := left;
				SrcTop := top;
			end;
		with DstRect do begin
				oldvloc := top;
				LineCount := 0;
				for vloc := top to bottom - 1 do begin
						v := SrcTop + (vloc - top) / rsVScale;
						vbase := trunc(v);
						vFraction := round((v - vbase) * scale);
						Info := SrcInfo;
						GetLine(SrcLeft, vbase, SrcWidth, SrcLine1);
						SrcLine1[SrcWidth] := SrcLine1[SrcWidth - 1];
						if vbase <> LastLine then begin
								GetLine(SrcLeft, vbase + 1, SrcWidth, SrcLine2);
								SrcLine2[SrcWidth] := SrcLine2[SrcWidth - 1];
							end;
						for hloc := left to right - 1 do begin
								hrel := hloc - left;
								h := hrel * scale2 div hscale;
								hbase := hrel * scale div hscale;
								hFraction := h mod scale;
								LowerAverage := SrcLine1[hbase] + hFraction * (SrcLine1[hbase + 1] - SrcLine1[hbase]) div scale;
								UpperAverage := SrcLine2[hbase] + hFraction * (SrcLine2[hbase + 1] - SrcLine2[hbase]) div scale;
								DstLine[hrel] := (LowerAverage + vfraction * (UpperAverage - LowerAverage) div scale);
							end;
						Info := DstInfo;
						PutLine(left, vloc, DstWidth, DstLine);
						LineCount := LineCount + 1;
						if LineCount >= CountsPerUpdate then begin
								LineCount := 0;
								SetRect(MaskRect, left, oldvloc, right, vloc + 1);
								UpdateScreen(MaskRect);
								oldvloc := vloc;
							end;
						if CommandPeriod then begin
								beep;
								exit(DoInterpolatedScaling)
							end;
					end; {for vloc:=}
				SetRect(MaskRect, left, oldvloc, right, vloc + 1);
				UpdateScreen(MaskRect);
			end;
	end;


	procedure ScaleUsingCopyBits;
		var
			srcPort: cGrafPtr;
			SavePort: GrafPtr;
			MaskRect: rect;
			SaveGDevice: GDHandle;
	begin
		with DstInfo^ do begin
				SaveGDevice := GetGDevice;
				SetGDevice(osGDevice);
				GetPort(SavePort);
				SetPort(GrafPtr(osPort));
				pmForeColor(BlackIndex);
				pmBackColor(WhiteIndex);
				srcPort := SrcInfo^.osPort;
				CopyBits(BitMapHandle(srcPort^.portPixMap)^^, BitMapHandle(osPort^.PortPixMap)^^, SourceRect, DstRect, gCopyMode, nil);
				pmForeColor(ForegroundIndex);
				pmBackColor(BackgroundIndex);
				SetPort(SavePort);
				SetGDevice(SaveGDevice);
			end;
		if UseSameWindow then begin
				MaskRect := DstRect;
				UpdateScreen(MaskRect);
			end;
	end;

	procedure DoRotate;
		var
			vloc, hloc: integer;
			hRel, vRel: integer;
			DoScaling: boolean;
			scale, scale2: LongInt;
			AngleInRadians: extended;
			SinAngle, CosAngle, vSinAngle, vCosAngle: LongInt;
			hTemp, vTemp, h, v: LongInt;
			sHSrcCenter, sVSrcCenter: LongInt;
			hdiv, vdiv, hmod, vmod: integer;
			xbase, ybase: integer;
			LowerLeft, LowerRight, UpperLeft, UpperRight: integer;
			xfraction, yfraction, UpperAverage, LowerAverage: LongInt;
			offset, NextUpdate: LongInt;
			SaveVLoc:integer;
			MaskRect:rect;
	begin
		scale := 1000;
		scale2 := scale div 2;
		AngleInRadians := -((rsAngle + 270.0) / 360.0) * 2.0 * pi;
		SinAngle := round(sin(AngleInRadians) * scale);
		CosAngle := round(cos(AngleInRadians) * scale);
		sHSrcCenter := hSrcCenter * scale;
		sVSrcCenter := vSrcCenter * scale;
		DoScaling := (rsHScale <> 1.0) or (rsVScale <> 1.0);
		SaveVLoc:=vStart;
		NextUpdate:=TickCount+6; {Update screen 10 times per second}
		for vloc := vStart to vEnd do begin
				vrel := vloc - vDstCenter;
				vSinAngle := vrel * SinAngle;
				vCosAngle := vrel * CosAngle;
				for hloc := hStart to hEnd do begin
						hrel := hloc - hDstCenter;
						htemp := hrel * SinAngle + vCosAngle;
						vtemp := vSinAngle - hrel * CosAngle;
						if DoScaling then begin
								htemp := round(htemp / rsHScale);
								vtemp := round(vtemp / rsVScale);
							end;
						h := htemp + sHSrcCenter;
						v := vtemp + sVSrcCenter;
						info := SrcInfo;
						if UseNearestNeighbor then
							value := MyGetPixel((h + scale2) div scale, (v + scale2) div scale)
						else begin{Use bilinear interpolation}
								xbase := h div scale;
								ybase := v div scale;
								with info^ do
									if (xbase < 0) or (ybase < 0) or (xbase >= (PixelsPerLine - 1)) or (ybase >= (nlines - 1)) then
										value := 0
									else begin
											offset := ybase * BytesPerRow + xbase;
											LowerLeft := ImageP(PicBaseAddr)^[offset];
											LowerRight := ImageP(PicBaseAddr)^[offset + 1];
											UpperLeft := ImageP(PicBaseAddr)^[offset + BytesPerRow];
											UpperRight := ImageP(PicBaseAddr)^[offset + BytesPerRow + 1];
											xFraction := h mod scale;
											yFraction := v mod scale;
											UpperAverage := UpperLeft * scale + (xfraction * (UpperRight - UpperLeft));
											LowerAverage := LowerLeft * scale + (xfraction * (LowerRight - LowerLeft));
											value := ((LowerAverage + yfraction * ((UpperAverage - LowerAverage) + scale2) div scale) + scale2) div scale;
										end;
							end;
						Info := DstInfo;
						PutPixel(hloc, vloc, value);
					end; {for hloc:=}
				if TickCount>=NextUpdate then begin
						SetRect(MaskRect, hstart, SaveVLoc, hend, vloc + 1);
						UpdateScreen(MaskRect);
						ShowAnimatedWatch;
						SaveVLoc:=vloc+1;
						NextUpdate:=TickCount+6;
					end;
				if CommandPeriod then begin
						beep;
						{$ifc not PowerPC} {ppc-bug}
						KillRoi;
						exit(ScaleAndRotate)
						{$endc}
					end; {for hloc:=}
			end; {for vloc:=}
		SetRect(MaskRect, hstart, SaveVLoc, hend, vend + 1);
		UpdateScreen(MaskRect);
	end;


begin
	if NotRectangular or NotInBounds then
		exit(ScaleAndRotate);
	if not (macro and not rsInteractive) then
		if not GetScaleAndAngle then
			exit(ScaleAndRotate);
	UpdatePicWindow;
	UseSameWindow := not rsCreateNewWindow;
	if UseSameWindow then
		with info^ do
			if NoUndo then begin
					AbortMacro;
					exit(ScaleAndRotate)
				end;
	with info^ do
		UseNearestNeighbor := rsMethod = NearestNeighbor;
	DrawTools;
	AutoSelectAll := not Info^.RoiShowing;
	if AutoSelectAll then
		SelectAll(true);
	ShowAnimatedWatch;
	if UseSameWindow then begin
			SetupUndo;
			WhatToUndo := UndoEdit;
			SetupUndoInfoRec;
			SrcInfo := UndoInfo;
			DstInfo := Info;
			if rsAngle = 0.0 then
				DoOperation(EraseOp);
		end
	else
		SrcInfo := info;
	with info^ do begin
			SourceRect := RoiRect;
			DstRect := RoiRect;
		end;
	with SourceRect do begin
			SrcWidth := right - left;
			SrcHeight := bottom - top;
			hSrcCenter := left + (SrcWidth div 2);
			vSrcCenter := top + (SrcHeight div 2);
			DstWidth := SrcWidth;
			DstHeight := SrcHeight;
		end;
	if UseSameWindow then
		with DstRect, info^ do begin
				if rsHScale <> 1.0 then begin
						DstWidth := round(SrcWidth * rsHScale);
						SaveWidth := DstWidth;
						left := left - (DstWidth - SrcWidth) div 2;
						if DstWidth > PicRect.right then
							DstWidth := PicRect.right;
						if left < 0 then
							left := 0;
						right := left + DstWidth;
						if DstWidth <> SaveWidth then begin
								SrcWidth := round(SrcWidth * (DstWidth / SaveWidth));
								SourceRect.left := hSrcCenter - SrcWidth div 2;
								SourceRect.right := SourceRect.left + SrcWidth;
							end;
					end;
				if rsVScale <> 1.0 then begin
						DstHeight := round(SrcHeight * rsVScale);
						SaveHeight := DstHeight;
						top := top - (DstHeight - SrcHeight) div 2;
						if DstHeight > PicRect.bottom then
							DstHeight := PicRect.bottom;
						if top < 0 then
							top := 0;
						bottom := top + DstHeight;
						if DstHeight <> SaveHeight then begin
								SrcHeight := round(SrcHeight * (DstHeight / SaveHeight));
								SourceRect.top := vSrcCenter - SrcHeight div 2;
								SourceRect.bottom := SourceRect.top + SrcHeight;
							end;
					end
			end {with}
	else begin
			DstWidth := round(SrcWidth * rsHScale);
			DstHeight := round(SrcHeight * rsVScale);
			if not NewPicWindow('Untitled', DstWidth, DstHeight) then begin
					KillRoi;
					exit(ScaleAndRotate)
				end;
			DstInfo := info;
			DstRect := info^.PicRect;
		end;
	with DstRect do begin
			hStart := left;
			vStart := top;
			hDstCenter := left + (DstWidth div 2);
			vDstCenter := top + (DstHeight div 2);
		end;
	hend := hstart + DstWidth - 1;
	vend := vstart + DstHeight - 1;
	rotate := rsAngle <> 0.0;
	if not macro then
		ShowMessage(CmdPeriodToStop);
	StartTicks := TickCount;
	if not rotate and (rsMethod = NearestNeighbor) then
		ScaleUsingCopyBits
	else if not rotate and not UseNearestNeighbor then
		DoInterpolatedScaling
	else
		DoRotate;
	if not macro then
		ShowTime(StartTicks, DstRect, '');
	KillRoi;
	with info^ do begin
			changes := true;
			if not UseSameWindow and (PixMapSize > UndoBufSize) then
				PutWarning;
			if SpatiallyCalibrated and (not UseSameWindow) then begin
					xScale := xScale * (DstWidth / SrcWidth);
					PixelAspectRatio := PixelAspectRatio * rsHScale / rsVScale;
					yScale := xScale / PixelAspectRatio;
				end;
		end;
	if not UseSameWindow and AutoSelectAll then begin
			SaveInfo := Info;
			Info := SrcInfo;
			KillRoi;
			Info := SaveInfo;
		end;
	if UseSameWindow then
		with NoInfo^ do begin
				roiType := RectRoi;
				RoiRect := DstRect;
				RectRgn(roiRgn, DstRect);
			end;
end;


function DoRankDialogBox: boolean;
	const
		MedianID = 5;
		MinID = 6;
		MaxID = 7;
		OpenID = 8;
		CloseID = 9;
		IterationsID = 11;
	var
		mylog: DialogPtr;
		item: integer;

	procedure SetButtons;
	begin
		SetDlogItem(mylog, MedianID, ord(RankFilter = MedianRank));
		SetDlogItem(mylog, MinID, ord(RankFilter = MinRank));
		SetDlogItem(mylog, MaxID, ord(RankFilter = MaxRank));
		SetDlogItem(mylog, OpenID, ord(RankFilter = OpeningRank));
		SetDlogItem(mylog, CloseID, ord(RankFilter = ClosingRank));
	end;

begin
	InitCursor;
	mylog := GetNewDialog(260, nil, pointer(-1));
	SetButtons;
	SetDNum(mylog, IterationsID, RankIterations);
	SelectdialogItemText(mylog, IterationsID, 0, 32767);
	repeat
		ModalDialog(nil, item);
		if (item >= MedianID) and (item <= CloseID) then begin
				if item = MedianID then
					RankFilter := MedianRank
				else if item = MinID then
					RankFilter := MinRank
				else if item = MaxID then
					RankFilter := MaxRank
				else if item = OpenID then
					RankFilter := OpeningRank
				else
					RankFilter := ClosingRank;
				SetButtons;
			end;
		if item = IterationsID then begin
				RankIterations := GetDNum(mylog, IterationsID);
				if RankIterations < 1 then
					RankIterations := 1;
				SetDNum(mylog, IterationsID, RankIterations);
			end;
	until (item = ok) or (item = cancel);
	DisposeDialog(mylog);
	DoRankDialogBox := item = ok;
end;


procedure DoRankFilter;
	var
		t: FateTable;
		i: integer;
begin
	if not macro then
		if not DoRankDialogBox then
			exit(DoRankFilter);
	case RankFilter of
		MedianRank: 
			for i := 0 to RankIterations - 1 do begin
					Filter(ReduceNoise, i, t);
					if CommandPeriod then
						leave;
				end;
		MinRank: 
			for i := 0 to RankIterations - 1 do begin
					Filter(MinRankFilter, i, t);
					if CommandPeriod then
						leave;
				end;
		MaxRank: 
			for i := 0 to RankIterations - 1 do begin
					Filter(MaxRankFilter, i, t);
					if CommandPeriod then
						leave;
				end;
		OpeningRank:  begin
				for i := 0 to RankIterations - 1 do begin
						Filter(MinRankFilter, i, t);
						if CommandPeriod then
							leave;
					end;
				for i := RankIterations to RankIterations * 2 - 1 do begin
						Filter(MaxRankFilter, i, t);
						if CommandPeriod then
							leave;
					end;
			end;
		ClosingRank:  begin
				for i := 0 to RankIterations - 1 do begin
						Filter(MaxRankFilter, i, t);
						if CommandPeriod then
							leave;
					end;
				for i := RankIterations to RankIterations * 2 - 1 do begin
						Filter(MinRankFilter, i, t);
						if CommandPeriod then
							leave;
					end;
			end
	end;
end;


procedure DoShadowFilter;
	var
		mylog: DialogPtr;
		item: integer;
		t: FateTable;
begin
	mylog := GetNewDialog(270, nil, pointer(-1));
	repeat
		ModalDialog(nil, item);
	until ((item >=1) and (item<=9));
	DisposeDialog(mylog);
	case item of
		1: Filter(ShadowN,  0, t);
		2: Filter(ShadowNE, 0, t);
		3: Filter(ShadowE,  0, t);
		4: Filter(ShadowSE, 0, t);
		5: Filter(ShadowS,  0, t);
		6: Filter(ShadowSW, 0, t);
		7: Filter(ShadowW,  0, t);
		8: Filter(ShadowNW, 0, t);
		otherwise;
	end;
end;


end.