(************************************************************************) (* an example programme for a Photoshop acquisition plug-in *) (* by Daniel W. Rickey *) (* London, Ontario, CANADA *) (* drickey@irus.rri.uwo.ca *) (* written in Think Pascal *) (* based on an MPW example programme by Thomas Knoll *) (* last modified 1994-04-27 *) (* *) (* Note that this Plug-in assumes that System 7.0 or newer is used *) (************************************************************************) Unit ExampleAcquisition; Interface Uses AcquireInterface; {When a plug-in module is called, Adobe Photoshop opens the resource fork} {of the plug-in file, loads the resource into to memory and locks it.} {The plug-in is then called starting at the first byte of the resource with the} {following calling convention:} Procedure Main (selector: Integer; AcquireRecord: AcquireRecordPtr; Var myData: Handle; Var result: Integer); Implementation Const kAbortAcquisition = 1; {an error occurred or the user selected cancel} (* the following are key codes to be used in dialogue box filters *) kEnterKey = $03; kBackSpace = $08; kTabKey = $09; kReturnKey = $0D; kEscKey = $1B; kLeftArrow = $1C; kRightArrow = $1D; kUpArrow = $1E; kDownArrow = $1F; kDeleteKey = $7F; (* these are friendly keys and are passed through the filters *) kControlKeys = [kBackSpace, kTabKey, kLeftArrow, kRightArrow, kUpArrow, kDownArrow, kDeleteKey]; Type {keep all of our variables in one record handle} {this will be used instead of keeping global variables} myDataType = Record Acquire: AcquireRecordPtr; Result: Integer; LastRows: Integer; {keep track of the image size and type} LastCols: Integer; {If the plug-in is called more than once, the last} LastMode: Integer; {set of parameters will be presented to the user.} ImageRow: Integer; {Image row being acquired; 1..Number of rows} ImagePlane: Integer; {image plane being acquired; 0..3} End; myDataPtr = ^myDataType; myDataHand = ^myDataPtr; (**** Calls the host's TestAbort function ****) (**** may call this several times a second to allow the user to abort the acquisition ****) (**** this also changes the cursor to a watch and periodically moves the watch hands ****) Function TestAbort (myData: myDataHand): Boolean; Var address: ProcPtr; Function DoTestAbort (codeAddress: ProcPtr): Boolean; Inline $205F, $4E90; Begin TestAbort := FALSE; Exit(TestAbort); address := myData^^.Acquire^.abortProc; TestAbort := DoTestAbort(address); End; {TestAbort} (**** Calls the host's UpdateProgress procedure ****) (**** The first parameter is the number of operations completed ****) (**** the second is the total number of operations to do ****) Procedure UpdateProgress (NumberDone, total: LongInt; myData: myDataHand); Var address: ProcPtr; Procedure DoUpdateProgress (NumberDone, total: LongInt; CodeAddress: ProcPtr); Inline $205F, $4E90; Begin address := myData^^.Acquire^.ProgressProc; DoUpdateProgress(NumberDone, total, address); End; {UpdateProgress} (**** Displays the about dialogue box for the plug-in module. ****) Procedure DoAbout; Const kDialogueID = 16000; Var theDialogueHand: DialogTHndl; theDialogue: DialogPtr; theItem: Integer; Begin {load the dialgue resource into memory and lock it} theDialogueHand := DialogTHndl(GetResource('DLOG', kDialogueID)); HNoPurge(Handle(theDialogueHand)); {get our about dialogue from the resource} theDialogue := GetNewDialog(kDialogueID, Nil, WindowPtr(-1)); {display the dialogue and wait for the user to select okay} ModalDialog(Nil, theItem); {get rid of the dialogue window and the resource} DisposDialog(theDialogue); HPurge(Handle(theDialogueHand)) End; {DoAbout} (**** Prepare to acquire an image. If the plug-in module needs only a limited ****) (**** amount of memory, it can lower the value of the 'maxData' field. ****) Procedure DoPrepare (myData: myDataHand); Begin {Photoshop initialises maxData to the maximum of number of bytes it can free} {divide by two to give half to photoshop and half to the plug-in} myData^^.Acquire^.maxData := myData^^.Acquire^.maxData Div 2; End; {DoPrepare} (**** outlines the OK button in a dialogue box ****) (**** this is from the new inside mac, Toolbox essentials ****) Procedure OutlineOKbutton (theDialogue: DialogPtr; TheItem: Integer); Const kOkayButton = 1; kButtonFrameInset = -4; kButtonFrameSize = 3; Var itemType: Integer; itemRect: Rect; ItemHandle: Handle; CurrentPen: PenState; ButtonOval: Integer; OldPort: WindowPtr; thePattern: Pattern; Begin GetDItem(theDialogue, kOkayButton, itemType, ItemHandle, itemRect); GetPort(OldPort); SetPort(ControlHandle(itemHandle)^^.ContrlOwner); GetPenState(CurrentPen); PenNormal; InsetRect(itemRect, kButtonFrameInset, kButtonFrameInset); FrameRoundRect(itemRect, 16, 16); ButtonOval := (ItemRect.Bottom - ItemRect.Top) Div 2 + 2; {get the black pen pattern from the system resource} GetIndPattern(thePattern, 0, 1); {set the pen pattern to black; can not use "Black" since it is an A5 QD global} PenPat(thePattern); PenSize(kButtonFrameSize, kButtonFrameSize); FrameRoundRect(itemRect, ButtonOval, ButtonOval); SetPenState(CurrentPen); SetPort(OldPort); End; {OutlineOKbutton} (**** filter key strokes function for the image parameters dialogue window ****) (**** allows only numbers to be entered in the image width and height text boxes ****) Function myImageParametersFilter (theDialogue: DialogPtr; Var TheEvent: EventRecord; Var ItemHit: Integer): Boolean; Const kDialogueID = 16001; kOkayItem = 1; kCancelItem = 2; kImageWidthItem = 3; kImageHeightItem = 4; kBitMapItem = 5; kGreyScaleItem = 6; kColourItem = 7; kRGBcolourItem = 8; kOkayOutlineItem = 13; DefaultItem = kOkayItem; Var Key: Integer; TextField: Integer; Begin myImageParametersFilter := FALSE; (* check if a key was pressed, or an autoRepeat happened *) If (TheEvent.What = KeyDown) Or (TheEvent.What = AutoKey) Then Begin (* check what text field the keystokes are occuring in *) TextField := DialogPeek(TheDialogue)^.EditField + 1; {get the key that was pressed} Key := band(TheEvent.Message, CharCodeMask); {if the key was a control key, or in the range 0 to 9, then do not filter it} If (Key In kControlKeys) Or (Key In [Ord('0')..Ord('9')]) Then Begin myImageParametersFilter := FALSE; End {if the enter or return key was pressed, then pass back okay button selected} Else If Key In [kEnterKey, kReturnKey] Then Begin myImageParametersFilter := TRUE; ItemHit := DefaultItem; End {if we are in a text field and a non-numeric key is pressed then filter} Else If (TextField In [kImageWidthItem, kImageHeightItem]) And Not (Key In [Ord('0')..Ord('9')]) Then Begin myImageParametersFilter := TRUE; End; End; {If KeyDown} End; {myImageParametersFilter} (**** Converts a string to a number between 1 and 30000 ****) (**** Returns false if not a valid number or out of range ****) Function ConvertString (theString: Str255; Var theNumber: Integer): Boolean; Var Loop: Integer; x: LongInt; Begin ConvertString := FALSE; {loop through all of the characters in the string and make sure all are numbers} {we do not really need to do this as the dialogue filter should have filtered} {all non-numeric characters} For Loop := 1 To Length(theString) Do Begin If Not (theString[Loop] In ['0'..'9']) Then Begin EXIT(ConvertString); End; End; {convert the string to a long integer} StringToNum(theString, x); {make sure the number is reasonable } If (x > 0) And (x < 30001) Then Begin ConvertString := TRUE; theNumber := x; End; End; {ConvertString} (**** Prompt the user for the image parameters; Returns false if the user cancels ****) Function GetImageParameters (Var ImageWidth, ImageHeight, mode: Integer): BOOLEAN; Const kDialogueID = 16001; kOkayItem = 1; kCancelItem = 2; kImageWidthItem = 3; kImageHeightItem = 4; kBitMapItem = 5; kGreyScaleItem = 6; kColourItem = 7; kRGBcolourItem = 8; kOkayOutlineItem = 13; Var itemRect: Rect; WidthString, HeightString: Str255; ItemHandle: Handle; TheDialogue: DialogPtr; TheDialogueHand: DialogTHndl; TheItem: Integer; itemType: Integer; WidthText: Handle; HeightText: Handle; bButton: ControlHandle; gButton: ControlHandle; iButton: ControlHandle; cButton: ControlHandle; Done: Boolean; Begin Done := FALSE; {load our dialogue resource into memory to make sure we do not} {use another plug-in's dialogue} TheDialogueHand := DialogTHndl(GetResource('DLOG', kDialogueID)); HNoPurge(Handle(TheDialogueHand)); {get our dialogue from the dialogue resource} TheDialogue := GetNewDialog(kDialogueID, Nil, WindowPtr(-1)); {get a handle to the "user" button outline item} GetDItem(TheDialogue, kOkayOutlineItem, itemType, ItemHandle, itemRect); {install the drawing routine for the application defined button outline} SetDItem(TheDialogue, kOkayOutlineItem, itemType, Handle(@OutlineOKbutton), itemRect); {set the image height value in the text edit box} GetDItem(TheDialogue, kImageHeightItem, itemType, HeightText, itemRect); NumToString(ImageHeight, HeightString); SetIText(HeightText, HeightString); {set the image width value in the text edit box} GetDItem(TheDialogue, kImageWidthItem, itemType, WidthText, itemRect); NumToString(ImageWidth, WidthString); SetIText(WidthText, WidthString); {select all of the text in the Image width text edit box} SelIText(TheDialogue, kImageWidthItem, 0, 32767); Repeat {set the states of the four image mode radio-buttons} GetDItem(TheDialogue, kBitMapItem, itemType, Handle(bButton), itemRect); SetCtlValue(bButton, ORD(mode = acquireModeBitmap)); GetDItem(TheDialogue, kGreyScaleItem, itemType, Handle(gButton), itemRect); SetCtlValue(gButton, ORD(mode = acquireModeGrayScale)); GetDItem(TheDialogue, kColourItem, itemType, Handle(iButton), itemRect); SetCtlValue(iButton, ORD(mode = acquireModeIndexedColor)); GetDItem(TheDialogue, kRGBcolourItem, itemType, Handle(cButton), itemRect); SetCtlValue(cButton, ORD(mode = acquireModeRGBColor)); {wait for the user to select something} ModalDialog(@myImageParametersFilter, theItem); Case theItem Of kBitMapItem: Begin mode := acquireModeBitmap; End; kGreyScaleItem: Begin mode := acquireModeGrayScale; End; kColourItem: Begin mode := acquireModeIndexedColor; End; kRGBcolourItem: Begin mode := acquireModeRGBColor; End; kOkayItem: {user selected okay button or pressed the return key} Begin {get both the image width and image height strings from the text edit boxes} GetIText(WidthText, WidthString); GetIText(HeightText, HeightString); {convert the width and height strings to numbers} {if an error then beep & select the offending text} If Not ConvertString(WidthString, ImageWidth) Then Begin SelIText(TheDialogue, kImageWidthItem, 0, 32767); SysBeep(1); End Else If Not ConvertString(HeightString, ImageHeight) Then Begin SelIText(TheDialogue, kImageHeightItem, 0, 32767); SysBeep(1); End Else Begin {if there were no errors, then return true and exit the loop} GetImageParameters := TRUE; Done := TRUE; End; End; kCancelItem: Begin {user selected cancel, so exit and return a false value} GetImageParameters := FALSE; Done := TRUE; End; Otherwise Begin End; End; {Case theItem} Until Done; {get rid of our dialogue window} DisposDialog(TheDialogue); {remove our dialogue resource from memory} HPurge(Handle(TheDialogueHand)); End; {GetImageParameters} (**** Asks the user and the returns the image parmaters to the calling program ****) (**** This call lets Photoshop know the mode, size and resolution of the image ****) (**** being returned, so Photoshop can allocate and initialise its data structures ****) (* The plug-in will display the dialogue box during this call and set the ****) (**** imageMode, imageSize,depth, planes, imageHRes and imageVRes fields. ****) (**** For an indexed color image, the redLUT , greenLUT and blueLUT fields are set ****) (**** If a duotone mode image is being acquired, the duotoneInfo field is set ****) Procedure DoStart (myData: myDataHand); Var j: Integer; Begin With myData^^, myData^^.Acquire^ Do Begin imageSize.v := LastRows; imageSize.h := LastCols; imageMode := LastMode; (* get the image size and mode from the user; exit if canceled *) If Not GetImageParameters(imageSize.v, imageSize.h, imageMode) Then Begin Result := kAbortAcquisition; Exit(DoStart) End; (* keep track of the user's choices for next time this is called *) LastRows := imageSize.v; LastCols := imageSize.h; LastMode := imageMode; (* start with image plane 0; = red for RGB images *) ImagePlane := 0; (* start with the first row of the image plane *) ImageRow := 1; (* initialise the host's image parameter record with appropriate values *) Case ImageMode Of acquireModeBitmap: Begin Depth := 1; (* 1 Bit per sample/pixel *) Planes := 1; (* 1 sample per pixel *) imageHRes := FixRatio(300, 1); (* 300 Horizontal Pixels per inch *) imageVRes := FixRatio(300, 1); (* 300 Vertical Pixels per inch *) End; acquireModeGrayScale: Begin Depth := 8; (* 8 Bits per sample/pixel *) Planes := 1; (* 1 sample per pixel *) imageHRes := FixRatio(72, 1); (* 72 Horizontal Pixels per inch *) imageVRes := FixRatio(72, 1); (* 72 Vertical Pixels per inch *) End; acquireModeIndexedColor: Begin Depth := 8; Planes := 1; imageHRes := FixRatio(72, 1); imageVRes := FixRatio(72, 1); {initialise the Look-Up-Tables that are used for indexed colours only} {the following is an arbitrary colour LUT} For j := 0 To 255 Do Begin redLUT[j] := CHR(j); greenLUT[j] := CHR(255 - j); blueLUT[j] := CHR(j Div 2) End; End; acquireModeRGBColor: Begin Depth := 8; Planes := 3; imageHRes := FixRatio(72, 1); imageVRes := FixRatio(72, 1); End; acquireModeCMYKColor: Begin End; acquireModeHSLColor: Begin End; acquireModeHSBColor: Begin End; acquireModeMultichannel: Begin End; acquireModeDuotone: Begin End; Otherwise Begin Result := kAbortAcquisition; End; End; {Case ImageMode} End; {With} End; {DoStart} (**** This call returns a strip of the image to Photoshop. ****) (**** Photoshop will continue to call this routine until an error is returned ****) (**** or the data field is set to Nil.*) Procedure DoContinue (myData: myDataHand); Var TheStripPtr: Ptr; aRow: LongInt; aColumn: LongInt; RowsInStrip: LongInt; Begin With myData^^, myData^^.Acquire^ Do Begin {if any data is left from the previous call, dispose of it} If data <> Nil Then Begin DisposPtr(data); data := Nil End; (* the plug-in returns the image data in the "data" field of the acquisition record *) (* the image can be returned in many strips; maximum strip size is set by "maxData" *) (* The area of the image strip being returned is specified by theRect and by the loPlane and hiPlane fields. *) (* the byte spacing between columns; we will send each image plane separately *) colBytes := 1; (* this is the number of bytes in one image row, i.e., the byte spacing between rows *) If depth = 8 Then Begin rowBytes := imageSize.h End Else Begin rowBytes := BSR(imageSize.h + 7, 3); End; (* determine the maximum number of image rows that can fit into a strip *) RowsInStrip := maxData Div rowBytes; {make sure the size of a single row is not larger than the biggest strip buffer} If RowsInStrip < 1 Then Begin Result := memFullErr; EXIT(DoContinue) End; (* acquire/generate image data for all image planes *) If ImagePlane < planes Then Begin {tell the host what image plane is being returned; RGB images are one colour per plane} loPlane := ImagePlane; hiPlane := ImagePlane; (* make sure that the rows in the strip is not larger than the remaining image *) If RowsInStrip > (imageSize.v - ImageRow + 1) Then Begin RowsInStrip := imageSize.v - ImageRow + 1; End; (* allocate memory for the image strip *) data := NewPtrClear(RowsInStrip * rowBytes); (* check if the memory was allocated properly *) If data = Nil Then Begin Result := memFullErr; Exit(DoContinue) End; (* position and size of the image strip; Photoshop starts at zero thus the "-1" *) SetRect(theRect, 0, ImageRow - 1, imageSize.h, ImageRow + RowsInStrip - 1); (* have our pointer point to the start of the image strip memory *) TheStripPtr := data; (* loop through all of the rows in the image strip *) For aRow := ImageRow To ImageRow + RowsInStrip - 1 Do Begin (* check if the user has pressed command-period; exit if true *) If TestAbort(myData) Then Begin Result := kAbortAcquisition; Exit(DoContinue) End; {tell the host to update its progress bar} UpdateProgress(ImagePlane * ORD4(imageSize.v) + aRow, planes * ORD4(imageSize.v), myData); (* This is where values are assigned to the pixels in the image. *) (* One would normally read in an image from disc or a scanner or just*) (* generate a pretty picture using fractals or what-ever. *) (* Here we just generate a pretty picture. *) {$PUSH} {$R-} Case ImageMode Of {for a bitmapped image, set the values of 8 pixels at once} acquireModeBitmap: Begin (* loop through all bytes in one row of the image strip *) For aColumn := 0 To rowBytes - 1 Do Begin TheStripPtr^ := BSL($FF, BAND(aRow, 7)); (* point to the next byte in the image strip *) TheStripPtr := Ptr(ORD4(TheStripPtr) + 1); End; {For aColumn} End; {acquireModeBitmap} {place one byte in each pixel; only have one image plane to worry about} acquireModeGrayScale: Begin (* loop through all bytes/pixels in one row of the image strip *) For aColumn := 0 To rowBytes - 1 Do Begin TheStripPtr^ := BAND(aColumn + aRow, $FF); (* point to the next byte in the image strip *) TheStripPtr := Ptr(ORD4(TheStripPtr) + 1); End; {For aColumn} End; {acquireModeGrayScale} {place one byte in each pixel; only have one image plane to worry about} acquireModeIndexedColor: Begin (* loop through all bytes/pixels in one row of the image strip *) For aColumn := 0 To rowBytes - 1 Do Begin TheStripPtr^ := BAND(aRow + aColumn, $FF); (* point to the next byte in the image strip *) TheStripPtr := Ptr(ORD4(TheStripPtr) + 1); End; {For aColumn} End; {acquireModeIndexedColor} {for an RGB image, place different values in each of the 3 image planes} acquireModeRGBColor: Begin (* loop through all bytes/pixels in one row of the image strip *) For aColumn := 0 To rowBytes - 1 Do Begin Case ImagePlane Of 0: TheStripPtr^ := BAND(aColumn, $FF); 1: TheStripPtr^ := BAND(aRow - 1, $FF); 2: TheStripPtr^ := BAND(aRow - 1 + aColumn, $FF); Otherwise Begin End; End; {Case ImagePlane} (* point to the next byte in the image strip *) TheStripPtr := Ptr(ORD4(TheStripPtr) + 1); End; {For aColumn} End; {acquireModeRGBColor} Otherwise Begin Result := kAbortAcquisition; End; End; {Case ImageMode} {$POP} End; {For aRow} {move to the start of the next image strip} ImageRow := ImageRow + RowsInStrip; {if we have reached the end of an image plane then move onto the next image plane} If ImageRow > imageSize.v Then Begin ImageRow := 1; ImagePlane := ImagePlane + 1 End; End; {If ImagePlane} End; {With} End; {DoContinue} (**** This routine will always be called if DoStart does not return an error ****) (**** even if DoContinue returns an error or the user aborts the operation ****) (**** This allows the Plug-In to delete the image strip buffer ****) Procedure DoFinish (Var myData: myDataHand); Begin {if the image strip data buffer is not empty, then delete it} If myData <> Nil Then Begin With myData^^.Acquire^ Do If data <> Nil Then Begin DisposPtr(data); data := Nil End; End; End; {DoFinish} (**** Main dispatching routine. Initialises and sets up the record variables,****) (**** and performs the operation specified by the host's selector. ****) (**** Think pascal wants this to be called "Main" to be placed first in the resource. ****) (**** other development systems may require that this be the first routine in the source code ****) Procedure Main (selector: Integer; AcquireRecord: AcquireRecordPtr; Var myData: Handle; Var result: Integer); Begin result := NoErr; { See if this is the first time called } {If so, allocate and initialise the record handle for our variables} If (myData = Nil) Then Begin {allocate memory for our record} myData := NewHandleClear(SizeOf(myDataType)); {check if the memory allocation was successful} If myData = Nil Then Begin result := MemFullErr; Exit(Main); End; { Initialize our plug-in values } myDataHand(myData)^^.LastRows := 256; myDataHand(myData)^^.LastCols := 256; myDataHand(myData)^^.LastMode := acquireModeRGBColor End; {lock the handle just to be certain} HLock(myData); {keep track of the pointer to the AcquireRecord} myDataHand(myData)^^.Acquire := AcquireRecord; {no error - yet} myDataHand(myData)^^.Result := noErr; {perform the requested action} Case selector Of acquireSelectorAbout: {show the about dialogue} DoAbout; acquireSelectorPrepare: {set things up for acquisition} DoPrepare(myDataHand(myData)); acquireSelectorStart: {get parameters from the user} DoStart(myDataHand(myData)); acquireSelectorContinue: {acquire or produce the image} DoContinue(myDataHand(myData)); acquireSelectorFinish: {tidy things up before modual is disposed of} DoFinish(myDataHand(myData)); Otherwise myDataHand(myData)^^.Result := acquireBadParameters End; {Case selector} {return the result of the request} result := myDataHand(myData)^^.Result; {unlock the handle before returning to the host programme} HunLock(myData); End; {Main} End.