Pascal Examples, Techniques & Operations

Users can use User.p

The User.p module is a good candidate for the placement of pascal source code which you develop. Since the User.p module is strategically placed in the build order below other modules you can call just about any routine in the rest of the project. Be sure to add the module name which contains the routine you are calling to the uses command in User.p
  QuickDraw, Palettes, PrintTraps, globals, Utilities, Graphics; <=== add module name 
here if you need to. Example would be File1, File2 or any other unit.

Recommended addition when adding to pascal

If you plan on modifying any of the pascal units, I would personally recommend that you add two comment lines to each and every pascal modification that you do. These are:
{Begin Modification}
{End Modification}
You won't regret it later when you go through code you wrote a year or two ago, or if you try and read somebody else's code. It is easy to use the find utility to find your old or other peoples modifications by searching on "begin modifications'.

Returning a value from pascal to a macro

One method for returning a calculated value from a pascal routine back into a macro is to use the rUser1 or rUser2 arrays. You can return real numbers and many of them if you need too.

In Pascal have:
User1^[1] := MyReturnValue;
In the macro have:
ReturnedValue := rUser1[1];
Or if you desire seeing the output in the results window you could have a macro like this:
Macro 'Show table';
  SetUser1Label('My 5 calc values');

Pascal versions of SelectSlice & SelectPic

SelectSlice is available directly in pascal. You might set something up like the following:
if Info^.StackInfo <> nil then
  SliceCount := Info^.StackInfo^.nSlices
else SliceCount := 1; for SliceNumber := 1 to SliceCount do begin SelectSlice(SliceNumber);

For SelectPic you might copy this code (taken from macros source file) and pass the PictureNumber to the routine (i.e. for PictureNumber:=1 to nPics):
 procedure SelectImage (id: integer);
  Info := pointer(WindowPeek(PicWindow[id])^.RefCon);

Putmessage, showmessage & PutmessageWithCancel


PutMessage is perhaps one of the easiest ways to provide feedback to users. To use putmessage you simply call the routine with the message or string you wish to give to the user.
    PutMessage('Capturing requires a Data Translation or SCION frame grabber card.');
You can pass multiple arguments with PutMessage.
   PutMessage(concat('Have a ', 'Nice day'));
or even something like:
   PutMessage(concat('Number of Objects:  ', Long2Str(ObjectCount)));


PutMessageWithCancel allows you to choose the path you might want to take in your code. Unlike putmessage, it allows you to press a cancel button. This might indicate that you should exit your procedure, such as in this example:
item: integer; begin item := PutMessageWithCancel('Do you really want to do this operation?'); if item = cancel then exit(YourProcedure);


ShowMessage allows display of calculations, data, variables or whatever you cast as a string into the Info window.
   ShowMessage('No QuickTime');
or when more involved you must convert reals or integers into strings before issuing the command:
 str1 := concat('min=', long2str(CurrentMin), ' (', long2str(AbsoluteMin), ')', crStr, 'max=', long2str(CurrentMax), ' (', long2str(AbsoluteMax), ')');

ScaleFactor := 253.0 / (CurrentMax - CurrentMin);

RealToString(ScaleFactor, 1, 4, str2);

ShowMessage(concat(str1, crStr, 'scale factor= ', str2));

How to input a number

     function GetInt (message: str255; default: integer; var Canceled: boolean): integer;
     function GetReal (message: str255; default: extended; var Canceled: boolean): extended;
You probably don't want to develop an entire dialog routine just to pass a number into your procedure from the keyboard. Fortunately, you don't have to. A default dialog exists for getting integers and real numbers.
....{rest of code}
    EndLoopCount :=0;   {a default}
    EndLoopCount := GetInt('Enter number of iterations:',0,WasCanceled);
     if WasCanceled then  

Reading from disk

From disk to macro user arrays:

If you have tab delimited data which you want loaded into the macro User arrays, you can easily open the data with this routine. If you have more than two columns of data then use one or more of the other macro arrays. To use this routine copy it into User.p, set it up as a UserCode call and recompile Image. You have to add File2 (File2.p contains GetTextFile) to the Uses clause at the beginning of User.p. Note that this routine has been changed for version of Image 1.54 and above.

procedure OpenData;
  fname: str255;
  RefNum, nValues, i: integer;
  rLine: RealLine;
 if not GetTextFile(fname, RefNum) then
 InitTextInput(fname, RefNum);
 i := 1;
 while not TextEOF do
   GetLineFromText(rLine, nValues);
   User1^[i] := rLine[1];
   User2^[i] := rLine[2];
   i := i + 1;

If you want to see the data, take a look at the macro above in the section on returning a value from pascal to a macro.

To your own arrays:
The routine is just as applicable to those who wish to read data from disk into arrays of their own, and not the user arrays. If you have your own large arrays, you will need to allocate memory for the pointers. An example of this is shown in the section "Memory". You can open data to as many arrays as you allocate by replacing User1^[i]. Example:
while not TextEOF do begin
    GetLineFromText(rLine, nValues);
    xCoordinate^[i] := rLine[1];
    yCoordinate^[i] := rLine[2];
    zCoordinate^[i] := rLine[3];

Memory and pointer allocation

Show below is an example of dynamic memory allocation. If you plan on using a large array then you need to allocate memory for the task. You should free the memory when done.

Here is an example of allocating memory for pointer arrays in User.p:
{User global variables go here.}
  MyMaxCoordinates = 5000;

  CoordType = packed array[1..MyMaxCoordinates] of real;
  CoordPtr = ^CoordType;

  xCoordinate, yCoordinate, zCoordinate: CoordPtr;

procedure YourAllocationCode;
  xCoordinate := CoordPtr(NewPtr(SizeOf(CoordType)));
  yCoordinate := CoordPtr(NewPtr(SizeOf(CoordType)));
  zCoordinate := CoordPtr(NewPtr(SizeOf(CoordType)));
  if (XCoordinate = nil) or (yCoordinate = nil) or (zCoordinate = nil) then begin
    PutMessage('Insufficient memory. Use get info and allocate more memory to Image');

If you don't need the pointer anymore you can free memory using the DisposPtr call.

Operating on an Image

The global variables below relate directly to handling of images. The entire PicInfo record is not displayed. The actual record contains a number of other useful image parameters and can be seen in the globals.p file of the image project. Familiarity with the data structure is advisable to those who plan on modifying or operating on the image in any manner.
    PicInfo = record
       nlines, PixelsPerLine: integer;
       ImageSize: LongInt;
       BytesPerRow: integer;
       PicBaseAddr: ptr;
       PicBaseHandle: handle;
       ...... {many others covered, in part, in other sections}

   InfoPtr = ^PicInfo;

    Info: InfoPtr;
var Info: InfoPtr;
Using this global structure allows for the simple use of
with Info^ do begin

Getting at the bytes of an image

Any number of techniques can be used to access the image for use or modification purposes. Several techniques and examples are listed below. The choice for which to use largely depends upon the application at hand.

Pascal routines such as GetLine use the LineType. First look at the definition of LineType. LineType is globally declared as:
 LineType = packed array[0..MaxLine] of UnsignedByte;

Naturally, UnsignedByte has been type defined as:
UnsignedByte = 0..255;
Pascal Technique one: :Use Apple's "CopyBits" to wholesale copy a ROI, memory locations, or an entire image. Example's of CopyBits can be seen in the Image source code Paste procedure, some of the video capture routines and many others.

Pascal Technique two: Use ApplyTable to change pixels from their current value to pixels of another value. You fill the table with your function. The simple example below, which is extracted from DoArithmetic, would add a constant value to the image. The index of the table is the old pixel value and tmp is the new pixel value. With ApplyTable you don't have to work with a linear function like adding a constant. You basically can apply any function you like. Of course, you would want to always check and see if you are above 255 or below zero and truncate as needed. The actual ApplyTable procedure calls assembly coded routines in applying the function to the image.

Technique 2 example
procedure SimpleUseOfApplyTable;
     table: LookupTable;
     i: integer;
     tmp: LongInt;
     Canceled: boolean;
  constant := GetReal('Constant to add:', 25, Canceled);
    for i := 0 to 255 do begin
       tmp := round(i + constant);
        if tmp < 0 then
          tmp := 0;
        if tmp > 255 then
          tmp := 255;
       table[i] := tmp;

Aside from "doing arithmetic" such as adding and subtracting, the AppyTable routine is used by Image to apply the Look Up Table (LUT) to the image. Changing the LUT, such as by contrast enhancement or using the LUT tool, doesn't change the bytes of the image until the menu selection "Apply LUT" is selected from the Enhance menu.

Technique Three:
A: Use a procedure such as GetLine to move sequentially down lines of the image. You can access each line as an array. Compiled pascal is obviously much than a macro at doing this. In addition, your macro can call the faster compiled pascal code.

B: Use the Picture base address, offset to current location, and Apple's Blockmove to access individual lines of the image. Again, each line can be treated as an array allowing access to individual picture elements. Examples below.

First look at the definition of LineType. LineType is globally declared as:
    LineType = packed array[0..MaxLine] of UnsignedByte;

Naturally, UnsignedByte has been type defined as:
    UnsignedByte = 0..255;
For the technique 3 examples you can either:

1) Deal with the entire image and find it's width and height as:
with info^.PicRect do begin
width := right - left;
height := bottom - top;
vstart := top;
hstart := left;

2) Deal with just the ROI that you have created and use:
with info^.PicRect do begin
    width := right - left;
    height := bottom - top;
    vstart := top;
    hstart := left;
It is often useful to have your routine automatically define the entire image as the area which you will operate on. To automatically select the image you might do the following:

  AutoSelectAll: boolean;
AutoSelectAll := not info^.RoiShowing;
 if AutoSelectAll then
The false parameter is used to make an invisible ROI rather than the visible 'marching ants' typified by ROI selections. By first checking if an ROI exists, this code prevents overwrite of your specific ROI.

Technique 3A example

See specific examples in the procedure ExportAsText, DoInterpolatedScaling and others. See also the procedure GetLine.
procedure AnyOldProcedure;
    width, hloc, vloc: integer;
    theLine: LineType;
    with info^.RoiRect do begin
      width := right - left;
      for vloc := top to bottom - 1 do begin
        GetLine(left, vloc, width, theLine);
        for hloc := 0 to width - 1 do begin
          DoSomethingWithinTheLine   i.e. TheLine[hloc]    
Technique 3B example

This prolonged example will perform the same function as the 3a. It may or may not be easier for you to see how it functions, but should let you see how GetLine can do the job with a lot less programming. As usual some of the variables are seen in the globally declared PicInfo record.
procedure AnotherOldProcedure;
  OldLine,NewLine: LineType;
  SaveInfo: InfoPtr;
  p, dst: ptr;
  offset: LongInt;
  c,i: Integer;
 SaveInfo := Info;
with info^.PicRect do begin
    width := right - left;
    height := bottom - top;
    vstart := top;
    hstart := left;
if NewPicWindow('new window', width, height) then
  with SaveInfo^ do begin
    offset := LongInt(vstart) * BytesPerRow + hstart;
    p := ptr(ord4(PicBaseAddr) + offset);
    dst := Info^.PicBaseAddr;
    while i <= height do begin
         BlockMove(p, @OldLine, width);
         p := ptr(ord4(p) + BytesPerRow);
         while c <= Saveinfo^.pixelsperline do begin
           NewLine[c] := OldLine[c] {+ or -??-find a pixel and do what you want}
       BlockMove(@NewLine, dst, width);
       dst := ptr(ord4(dst) + width);
      end; {while i <= height}
   end; { with SaveInfo^}
The 3b example is an oversimplification of the function duplicate in the image project. It usually is a good idea to first create a new window to move your information to. The NewPicWindow procedure can do this. The dst pointer can point into the new windows memory.