Image Scanning on OS-X

Mac OSX specific forum
User avatar
Shardik
Addict
Addict
Posts: 1989
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: Image Scanning on OS-X

Post by Shardik »

Sorry, but although having worked the whole weekend on that problem, unfortunately I haven't been successful... :evil:
My above code example is working like a charm on Snow Leopard, but won't work on Lion, Mountain Lion or Mavericks. On the latter three only an empty ScannerView is displayed.

In contrast to that an Objective-C code example in Xcode with no code but only the 2 views IKDeviceBrowserView and IKScannerDeviceView in a window is working fine on all 4 MacOS versions...

At first I thought that it might be a problem of the introduction of constraints in window layout beginning with Lion, but setting setTranslatesAutoresizingMaskIntoConstraints to #YES in both IKDeviceBrowserView and IKScannerDeviceView as described here didn't help (this setting is already the default setting). And changing several other contraints settings didn't help neither. Also compiling to an app on Snow Leopard and starting this app on Mountain Lion or Mavericks didn't work.

My second attempt was to try out different delegates with their respective callbacks for ICDeviceBrowser, IKDeviceBrowserView and IKScannerDeviceView which didn't work neither. I found out that the callback established for deviceBrowser:didAddDevice:moreComing: was always working fine on all 4 MacOS versions but the callback for deviceBrowser:didRemoveDevice:moreGoing: was only called on SnowLeopard (when closing the app), but never on Lion, Mountain Lion and Mavericks...

By the way I found out that for detecting my scanner Brother ADS-2100, it was essential that at least one printer had been connected once and its drivers had been installed. Otherwise the scanner was not detected... :shock:

Sorry, TI-994A, for not being able to present the good news you were waiting for. :(
User avatar
TI-994A
Addict
Addict
Posts: 2512
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

Re: Image Scanning on OS-X

Post by TI-994A »

Hello Shardik, and thank you for your tireless help - I truly do appreciate it.

I find it strange that there's so little on the topic, almost like no one uses scanners on Macs.

Anyway, for the time being, my Xcode version of the scanner browser would have to suffice, called with PureBasic's RunProgram() function; at least I get title customisation, although it would be even better if I could set the scan destination and file name as well.

You mentioned trying out different delegates: I know that delegate protocols are added with the class_addMethod API function, but how do you set the delegates themselves? And how do you set multiple delegates? For example the ICScannerDeviceDelegate and IKScannerDeviceViewDelegate? Could you help me with a small code illustration?

I'll still be chipping away at this, and will keep you updated if there's any progress.

Once again, thank you Shardik. :D
Texas Instruments TI-99/4A Home Computer: the first home computer with a 16bit processor, crammed into an 8bit architecture. Great hardware - Poor design - Wonderful BASIC engine. And it could talk too! Please visit my YouTube Channel :D
User avatar
TI-994A
Addict
Addict
Posts: 2512
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

Re: Image Scanning on OS-X

Post by TI-994A »

Hi Shardik. I've been working on the code this past week, and I've gotten some progress. Instead of using the ImageKit browsers and views, I've tried to implement the more native functions of the ImageCaptureCore library. That way, the application is not relegated to using the built-in UI, and could perhaps get a little more control.

So far, I've been able to set the various delegate methods up, and the application is responding to devices being added and removed, and the devices are responding to open session requests through the proper protocol methods.

However, when a requestScan function is invoked, no actual scan is performed, although the proper response is received through the didCompleteScanWithError() method.

Here's the code:

Code: Select all

#ICDeviceLocationTypeMaskLocal     = $00000100
#ICDeviceLocationTypeMaskRemote    = $0000FE00
#ICDeviceTypeMaskScanner           = $00000002
#ICDeviceTypeScanner               = $00000002

ImportC ""
  class_addMethod(Class.i, Selector.i, *Callback, Types.P-ASCII)
  class_createInstance(Class.i, ExtraBytes.i)
  class_addProtocol(Class.i, Protocol.i)
  objc_allocateClassPair(ModelClass.i, NewClassName.P-ASCII, ExtraBytes.i)
  objc_lookUpClass(ClassName.P-ASCII)
  object_setClass(ObjectToModify.i, NewClass.i)
  objc_registerClassPair(NewClass.i)
  objc_getProtocol(ProtocolName.P-ASCII)
  sel_registerName(MethodName.P-ASCII)
EndImport

ImportC "/System/Library/Frameworks/ImageCaptureCore.framework/ImageCaptureCore"
EndImport

ImportC "/System/Library/Frameworks/Quartz.framework/Quartz"
EndImport

Enumeration
  #MainWindow
  #scannerList
  #scanButton
EndEnumeration

Global Dim scanners.i(0)
Global.i delegateClass, activeScanner, selectedScanner, scanning

Procedure scan()
  Debug "initiating scan..."
  scanning = 1
  CocoaMessage(0, activeScanner, "requestScan")    
EndProcedure

ProcedureC deviceDidBecomeReady(object.i, selector.i, device.i)
  Debug "selected device is ready..."  
  
  Protected scanArea.NSRect
  With scanArea
    \origin\x = 0
    \origin\y = 0
    \size\width = 10
    \size\height = 10
  EndWith

  scanFolder = CocoaMessage(0, 0, "NSURL fileURLWithPath:$", @"/Users/myFolder")
  scanFile = CocoaMessage(0, 0, "NSString stringWithString:$", @"myFile")
  
  CocoaMessage(0, device, "requestSelectFunctionalUnit:", 0)   ;0 for flatbed type
  CocoaMessage(0, activeScanner, "setTransferMode:", 0)        ;0 for  file-mode
  CocoaMessage(0, activeScanner, "setDownloadsDirectory:", scanFolder)
  CocoaMessage(0, activeScanner, "setDocumentName:$", @"MyScan")  
  CocoaMessage(0, activeScanner, "setDocumentUTI:$", @"kUTTypePNG")  
  CocoaMessage(0, CocoaMessage(0, activeScanner, 
                               "selectedFunctionalUnit"), "setMeasurementUnit:", 0)  
  CocoaMessage(0, CocoaMessage(0, activeScanner, 
                               "selectedFunctionalUnit"), "setScanArea:@", @scanArea)
EndProcedure

ProcedureC didAddDevice(object.i, selector.i, deviceBrowser.i, addedDevice.i, moreComing.i)
  Debug "device found and added..."
  
  deviceType.i = CocoaMessage(0, addedDevice, "type")    
  deviceName.s = PeekS(CocoaMessage(0, CocoaMessage(0, addedDevice, "name"), "UTF8String"), -1, #PB_UTF8)
  
  If moreComing = #NO And (deviceType & #ICDeviceTypeMaskScanner) = #ICDeviceTypeScanner
    newIndex = ArraySize(scanners()) + 1
    ReDim scanners(newIndex)
    scanners(newIndex) = addedDevice
    AddGadgetItem(#scannerList, -1, deviceName)
    CocoaMessage(0, addedDevice, "setDelegate:",
                 class_createInstance(delegateClass, 0))   ;PureBasic app controls the scanner
  EndIf
EndProcedure

ProcedureC didRemoveDevice(object.i, selector.i, deviceBrowser.i, removedDevice.i, moreGoing.i)
  Debug "device removed..."
  CocoaMessage(0, removedDevice, "requestCloseSession")
  CocoaMessage(0, removedDevice, "setDelegate:", 0)
  RemoveGadgetItem(#scannerList, selectedScanner - 1)
  ReDim scanners(ArraySize(scanners()) - 1)
EndProcedure

ProcedureC didOpenSessionWithError(object.i, selector.i, device.i, error.i)
  Debug "session opened... errors: " + Str(error)
EndProcedure

ProcedureC didCloseSessionWithError(object.i, selector.i, device.i, error.i)
  Debug "session closed... errors: " + Str(error)
EndProcedure

ProcedureC didSelectFunctionalUnit(object.i, selector.i, scannerDevice.i, functionalUnit.i, error.i)
  Debug "functional unit selected... errors: " + Str(error)
EndProcedure

ProcedureC didReceiveStatusInformation(object.i, selector.i, device.i, status.i)
  Debug "receiving status info..."
EndProcedure

ProcedureC didScanToURL(object.i, selector.i, scannerDevice.i, URL.i)  
  Debug "scanned to file: "+ PeekS(URL, -1, #PB_UTF8)
  scanning = 0
EndProcedure

ProcedureC didCompleteScanWithError(object.i, selector.i, scannerDevice.i, error.i)
  Debug "scan completed... errors: " + Str(error)
  scanning = 0
EndProcedure
  
ProcedureC didEncounterError(object.i, selector.i, device.i, error.i)
  Debug "encountered error: " + Str(error)
  scanning = 0
EndProcedure

wFlags = #PB_Window_SystemMenu | #PB_Window_ScreenCentered
OpenWindow(#MainWindow, 100, 100, 800, 500, "Scanner Application", wFlags)
ListViewGadget(#scannerList, 0, 0, 200, 500)
ButtonGadget(#scanButton, 220, 10, 100, 30, "SCAN")
SetGadgetColor(#scannerList, #PB_Gadget_BackColor, RGB(200, 200, 255))

deviceBrowser = CocoaMessage(0, 0, "ICDeviceBrowser new")

delegateClass = objc_allocateClassPair(objc_lookUpClass("NSObject"), "PB_Delegate", 0)   ;PureBasic app class

class_addMethod(delegateClass, 
                sel_registerName("deviceDidBecomeReady:"), @deviceDidBecomeReady(), "v@:@")
class_addMethod(delegateClass, 
                sel_registerName("device:didEncounterError:"), @didEncounterError(), "v@:@@")
class_addMethod(delegateClass, 
                sel_registerName("device:didOpenSessionWithError:"), @didOpenSessionWithError(), "v@:@@")
class_addMethod(delegateClass, 
                sel_registerName("device:didCloseSessionWithError:"), @didCloseSessionWithError(), "v@:@@")
class_addMethod(delegateClass, 
                sel_registerName("device:didReceiveStatusInformation:"), @didReceiveStatusInformation(), "v@:@@")
class_addMethod(delegateClass, 
                sel_registerName("deviceBrowser:didAddDevice:moreComing:"), @didAddDevice(), "v@:@@@")
class_addMethod(delegateClass, 
                sel_registerName("deviceBrowser:didRemoveDevice:moreGoing:"), @didRemoveDevice(), "v@:@@@")
class_addMethod(delegateClass, 
                sel_registerName("scannerDevice:didScanToURL:"), @didScanToURL(), "v@:@@")
class_addMethod(delegateClass, 
                sel_registerName("scannerDevice:didCompleteScanWithError:"), @didCompleteScanWithError(), "v@:@@")
class_addMethod(delegateClass, 
                sel_registerName("scannerDevice:didSelectFunctionalUnit:error:"), @didSelectFunctionalUnit(), "v@:@@@")

objc_registerClassPair(delegateClass)

CocoaMessage(0, deviceBrowser, "setDelegate:", class_createInstance(delegateClass, 0))   ;PureBasic app controls the browser
CocoaMessage(0, deviceBrowser, "setBrowsedDeviceTypeMask:", #ICDeviceLocationTypeMaskLocal | 
                                                            #ICDeviceLocationTypeMaskRemote |
                                                            #ICDeviceTypeMaskScanner)
CocoaMessage(0, deviceBrowser, "start")

Repeat
  Select WaitWindowEvent()
    Case #PB_Event_CloseWindow
      appQuit = 1
    Case #PB_Event_Gadget
      Select EventGadget()
        Case #scanButton
          scan()
        Case #scannerList
          Select EventType()
            Case #PB_EventType_LeftDoubleClick
              selectedScanner = GetGadgetState(#scannerList) + 1
              activeScanner = scanners(selectedScanner)
              CocoaMessage(0, activeScanner, "requestOpenSession")
          EndSelect
      EndSelect
  EndSelect
Until appQuit = 1

CocoaMessage(0, activeScanner, "release")
CocoaMessage(0, deviceBrowser, "stop")
CocoaMessage(0, deviceBrowser, "release")
I was hoping that you could take a look at it and see where I've gone wrong. I'm sure that it must be littered with syntactical errors, although I wouldn't know even if it hit me on the head. I've also not been able to set the measurement area or scan area of the functional unit, as it keeps throwing an Object is nil error.

If anyone else might have any ideas as well, I'd appreciate your help. It would be a pretty useful utility to have in the PureBasic toolbox; if we could get it to work.

Thanks! :D
Last edited by TI-994A on Thu Apr 30, 2015 7:16 am, edited 1 time in total.
Texas Instruments TI-99/4A Home Computer: the first home computer with a 16bit processor, crammed into an 8bit architecture. Great hardware - Poor design - Wonderful BASIC engine. And it could talk too! Please visit my YouTube Channel :D
User avatar
Shardik
Addict
Addict
Posts: 1989
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: Image Scanning on OS-X

Post by Shardik »

TI-994A,

unfortunately I don't have a scanner anymore for testing (only two scanners which aren't recognized neither by Apple's image capture app nor by ICDeviceBrowser), so I am "blind" again. It would be helpful if you could post your debug log, so that we are able to see which callbacks are called. Is "requestOpenSession" executed when selecting your scanner in #scannerList? If this is the case, your callback didOpenSessionWithError() should be called (even if no error occurred) otherwise the delegate isn't set correctly:
ICDevice.h wrote:@method requestOpenSession:
@abstract This message requests to open a session on the device. A client MUST open a session on a device in order to use the device.
@discussion Make sure the receiver's delegate is set prior to sending this message; otherwise this message will be ignored. This request is completed when the delegate receives a "device:didOpenSessionWithError:" message. No more messages will be sent to the delegate if this request fails.
User avatar
TI-994A
Addict
Addict
Posts: 2512
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

Re: Image Scanning on OS-X

Post by TI-994A »

Shardik wrote:...It would be helpful if you could post your debug log, so that we are able to see which callbacks are called...
Hi Shardik. Thank you for your reply, and my apologies for my late reply; I was away on project for a few days and I didn't have access to my dev systems.

Anyway, as you had requested, here are the output logs for the code that I had posted. As you can see, the method responses correspond to the API requirements, with each called in succession, in the proper sequence. :arrow:
debug wrote:on connecting a scanner:
1. device found and added...

on selecting the device from the list gadget:
2. session opened...
3. selected device is ready...
4. functional unit selected... errors: 0
5. functional unit selected... errors: 0

on clicking the SCAN button:
6. initiating scan...
7. encountered error: 429xxxxxxx
8. scan completed... errors: 429xxxxxxx

on disconnecting the scanner:
9. device removed...

on re-connecting the scanner:
10. device found and added...
Hope you might have some insight. :idea:

Further to this, did you find any implementation errors in my code? I'm not very familiar with the CocoaMessage() function, and as such am not sure if I've called half of the functions correctly.

:?: In particular are the scanner's setDownloadsDirectory, setDocumentName, and setDocumentUTI functions - is the file name and download directory set correctly, I mean using the fileURLWithPath and stringWithString types? And is the kUTTypePNG an actual string identifier or is it meant to be a constant?

:?: As for the selected functional unit's setMeasurementUnit and setScanArea functions - is that the correct method to reference the selected functional unit?

Code: Select all

CocoaMessage(0, CocoaMessage(0, activeScanner, "selectedFunctionalUnit"), "setScanArea:@", @scanArea)
Sorry for the barrage, but I'm truly lost. :?

Thank you Shardik. :D
Texas Instruments TI-99/4A Home Computer: the first home computer with a 16bit processor, crammed into an 8bit architecture. Great hardware - Poor design - Wonderful BASIC engine. And it could talk too! Please visit my YouTube Channel :D
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Image Scanning on OS-X

Post by wilbert »

TI-994A wrote:And is the kUTTypePNG an actual string identifier or is it meant to be a constant?
It's a NSString constant "public.png" so that line should be

Code: Select all

CocoaMessage(0, activeScanner, "setDocumentUTI:$", @"public.png")
Windows (x64)
Raspberry Pi OS (Arm64)
User avatar
TI-994A
Addict
Addict
Posts: 2512
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

Re: Image Scanning on OS-X

Post by TI-994A »

wilbert wrote:It's a NSString constant "public.png" so that line should be

Code: Select all

CocoaMessage(0, activeScanner, "setDocumentUTI:$", @"public.png")
Hi wilbert, and thank you for your quick reply. I'll give that a try. :)

Would you also know if the fileURLWithPath and stringWithString types are being used correctly to reference the file name and path? Is the current implementation correct:

Code: Select all

  scanFolder = CocoaMessage(0, 0, "NSURL fileURLWithPath:$", @"/Users/myFile")
  scanFile = CocoaMessage(0, 0, "NSString stringWithString:$", @"myFile")
Are the file separators [/] placed correctly and in the right places?

Thank you. :)
Texas Instruments TI-99/4A Home Computer: the first home computer with a 16bit processor, crammed into an 8bit architecture. Great hardware - Poor design - Wonderful BASIC engine. And it could talk too! Please visit my YouTube Channel :D
User avatar
Shardik
Addict
Addict
Posts: 1989
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: Image Scanning on OS-X

Post by Shardik »

TI-994A wrote:Would you also know if the fileURLWithPath and stringWithString types are being used correctly to reference the file name and path? Is the current implementation correct:

Code: Select all

  scanFolder = CocoaMessage(0, 0, "NSURL fileURLWithPath:$", @"/Users/myFile")
  scanFile = CocoaMessage(0, 0, "NSString stringWithString:$", @"myFile")
Are the file separators [/] placed correctly and in the right places?
To set the scanFolder correctly you have to use a trailing slash. These declarations are working for me:

Code: Select all

  scanFolder = CocoaMessage(0, 0, "NSURL fileURLWithPath:$", @"/Users/Shardik/Scans/")
  scanFile = CocoaMessage(0, 0, "NSString stringWithString:$", @"MyScan")
I have again been able to borrow from a colleague for this weekend a Brother ADS-2100 scanner for testing purposes. And after eliminating several bugs in your code example, I was finally able to successfully scan a page by using your modified code example on Snow Leopard and Mavericks... :P

My first test of your code example on Snow Leopard displayed the following debug and error messages:
Snow Leopard:
-------------
After starting the program:
- device found and added...

After double-click onto scanner Brother ADS-2100:
- session opened... errors: 0
- selected device is ready...
- functional unit selected... errors: 4322294496
- functional unit selected... errors: 0

After clicking onto "Scan":
- initiating scan...
- receiving status info...
- receiving status info...
- scan completed... errors: 0

After closing the program by clicking onto the red dot:
- [ERROR] ScanDemo.PB (Line: 181)
- [ERROR] -[PB_Delegate didRemoveDevice:]: unrecognized selector sent to instance 0x101a0b5d0
- The first error message after selecting the functional unit originated from your selection of a flatbed type scanner. My Brother ADS-2100 is a document feeder type, so I had to change 0 to 3 (the structure ICScannerFunctionalUnitType is defined in ICScannerFunctionalUnits.h).
- The second error on exiting the program caused a crash because you defined the callback for deviceBrowser:didRemoveDevice:moreGoing: but not the callback for didRemoveDevice:. After defining the missing callback your code example closed gracefully calling first didRemoveDevice: and afterwards deviceBrowser:didRemoveDevice:moreGoing: (on Snow Leopard).
- A third bug was the missing data: parameter in scannerDevice:didScanToURL:data: which resulted in not calling that callback.
- I have changed the PNG definiton as wilbert had proposed

There are still some quirks though (on which I am still working):
- On Mavericks the last line (release of the device browser) has to be commented out because otherwise the program will terminate with the error "Program terminated (by an external library)"
- On both Snow Leopard and Mavericks the lines with setMeasurementUnit: and setScanArea: throw a warning with "Object is nil."

But I absolutely wanted to let you know that at last we are nearly through... :lol:

This is your modified code example which runs on Snow Leopard and Mavericks and saves a complete scanned page as MyScan.png in /Users/Shardik/Scans on my iMac:

Code: Select all

#ICDeviceLocationTypeMaskLocal     = $00000100
#ICDeviceLocationTypeMaskRemote    = $0000FE00
#ICDeviceTypeMaskScanner           = $00000002
#ICDeviceTypeScanner               = $00000002

ImportC ""
  class_addMethod(Class.i, Selector.i, *Callback, Types.P-ASCII)
  class_createInstance(Class.i, ExtraBytes.i)
  class_addProtocol(Class.i, Protocol.i)
  objc_allocateClassPair(ModelClass.i, NewClassName.P-ASCII, ExtraBytes.i)
  objc_lookUpClass(ClassName.P-ASCII)
  object_setClass(ObjectToModify.i, NewClass.i)
  objc_registerClassPair(NewClass.i)
  objc_getProtocol(ProtocolName.P-ASCII)
  sel_registerName(MethodName.P-ASCII)
EndImport

ImportC "/System/Library/Frameworks/ImageCaptureCore.framework/ImageCaptureCore"
EndImport

ImportC "/System/Library/Frameworks/Quartz.framework/Quartz"
EndImport

Enumeration
  #MainWindow
  #scannerList
  #scanButton
EndEnumeration

Global Dim scanners.i(0)
Global.i delegateClass, activeScanner, selectedScanner, scanning

Procedure scan()
  Debug "initiating scan..."
  scanning = 1
  CocoaMessage(0, activeScanner, "requestScan")   
EndProcedure

ProcedureC deviceDidBecomeReady(object.i, selector.i, device.i)
  Debug "selected device is ready..." 
 
  Protected scanArea.NSRect
  With scanArea
    \origin\x = 0
    \origin\y = 0
    \size\width = 10
    \size\height = 10
  EndWith

  scanFolder = CocoaMessage(0, 0, "NSURL fileURLWithPath:$", @"/Users/Shardik/Scans/")
  scanFile = CocoaMessage(0, 0, "NSString stringWithString:$", @"MyScan")
 
  ; CocoaMessage(0, device, "requestSelectFunctionalUnit:", 0)   ;0 for flatbed type
  CocoaMessage(0, device, "requestSelectFunctionalUnit:", 3)   ;3 for document feeder type
  CocoaMessage(0, activeScanner, "setTransferMode:", 0)        ;0 for file-mode
  CocoaMessage(0, activeScanner, "setDownloadsDirectory:", scanFolder)
  CocoaMessage(0, activeScanner, "setDocumentName:$", @"MyScan") 
  CocoaMessage(0, activeScanner, "setDocumentUTI:$", @"public.png")
  CocoaMessage(0, CocoaMessage(0, activeScanner,
                               "selectedFunctionalUnit"), "setMeasurementUnit:", 0) 
  CocoaMessage(0, CocoaMessage(0, activeScanner,
                               "selectedFunctionalUnit"), "setScanArea:@", @scanArea)
EndProcedure

ProcedureC didAddDevice(object.i, selector.i, deviceBrowser.i, addedDevice.i, moreComing.i)
  Debug "device found and added..."
 
  deviceType.i = CocoaMessage(0, addedDevice, "type")   
  deviceName.s = PeekS(CocoaMessage(0, CocoaMessage(0, addedDevice, "name"), "UTF8String"), -1, #PB_UTF8)
 
  If moreComing = #NO And (deviceType & #ICDeviceTypeMaskScanner) = #ICDeviceTypeScanner
    newIndex = ArraySize(scanners()) + 1
    ReDim scanners(newIndex)
    scanners(newIndex) = addedDevice
    AddGadgetItem(#scannerList, -1, deviceName)
    CocoaMessage(0, addedDevice, "setDelegate:",
                 class_createInstance(delegateClass, 0))   ;PureBasic app controls the scanner
  EndIf
EndProcedure

ProcedureC didRemoveDevice(object.i, selector.i, removedDevice.i)
  Debug "device removed"
EndProcedure

ProcedureC didRemoveDeviceInBrowser(object.i, selector.i, deviceBrowser.i, removedDevice.i, moreGoing.i)
  Debug "device removed in browser..."
  CocoaMessage(0, removedDevice, "requestCloseSession")
  CocoaMessage(0, removedDevice, "setDelegate:", 0)
  RemoveGadgetItem(#scannerList, selectedScanner - 1)
  ReDim scanners(ArraySize(scanners()) - 1)
EndProcedure

ProcedureC didOpenSessionWithError(object.i, selector.i, device.i, error.i)
  Debug "session opened... errors: " + Str(error)
EndProcedure

ProcedureC didCloseSessionWithError(object.i, selector.i, device.i, error.i)
  Debug "session closed... errors: " + Str(error)
EndProcedure

ProcedureC didSelectFunctionalUnit(object.i, selector.i, scannerDevice.i, functionalUnit.i, error.i)
  Debug "functional unit selected... errors: " + Str(error)
EndProcedure

ProcedureC didReceiveStatusInformation(object.i, selector.i, device.i, status.i)
  Debug "receiving status info..."
EndProcedure

ProcedureC didScanToURL(object.i, selector.i, scannerDevice.i, URL.i, *data) 
  Debug "scanned to " +
    PeekS(CocoaMessage(0, CocoaMessage(0, URL, "absoluteString"), "UTF8String"),
    -1, #PB_UTF8)
  scanning = 0
EndProcedure

ProcedureC didCompleteScanWithError(object.i, selector.i, scannerDevice.i, error.i)
  Debug "scan completed... errors: " + Str(error)
  scanning = 0
EndProcedure
 
ProcedureC didEncounterError(object.i, selector.i, device.i, error.i)
  Debug "encountered error: " + Str(error)
  scanning = 0
EndProcedure

wFlags = #PB_Window_SystemMenu | #PB_Window_ScreenCentered
OpenWindow(#MainWindow, 100, 100, 800, 500, "Scanner Application", wFlags)
ListViewGadget(#scannerList, 0, 0, 200, 500)
ButtonGadget(#scanButton, 220, 10, 100, 30, "SCAN")
SetGadgetColor(#scannerList, #PB_Gadget_BackColor, RGB(200, 200, 255))

deviceBrowser = CocoaMessage(0, 0, "ICDeviceBrowser new")

delegateClass = objc_allocateClassPair(objc_lookUpClass("NSObject"), "PB_Delegate", 0)   ;PureBasic app class

class_addMethod(delegateClass,
                sel_registerName("didRemoveDevice:"), @didRemoveDevice(), "v@:@")
class_addMethod(delegateClass,
                sel_registerName("deviceDidBecomeReady:"), @deviceDidBecomeReady(), "v@:@")
class_addMethod(delegateClass,
                sel_registerName("device:didEncounterError:"), @didEncounterError(), "v@:@@")
class_addMethod(delegateClass,
                sel_registerName("device:didOpenSessionWithError:"), @didOpenSessionWithError(), "v@:@@")
class_addMethod(delegateClass,
                sel_registerName("device:didCloseSessionWithError:"), @didCloseSessionWithError(), "v@:@@")
class_addMethod(delegateClass,
                sel_registerName("device:didReceiveStatusInformation:"), @didReceiveStatusInformation(), "v@:@@")
class_addMethod(delegateClass,
                sel_registerName("deviceBrowser:didAddDevice:moreComing:"), @didAddDevice(), "v@:@@@")
class_addMethod(delegateClass,
                sel_registerName("deviceBrowser:didRemoveDevice:moreGoing:"), @didRemoveDeviceInBrowser(), "v@:@@@")
class_addMethod(delegateClass,
                sel_registerName("scannerDevice:didScanToURL:data:"), @didScanToURL(), "v@:@@@")
class_addMethod(delegateClass,
                sel_registerName("scannerDevice:didCompleteScanWithError:"), @didCompleteScanWithError(), "v@:@@")
class_addMethod(delegateClass,
                sel_registerName("scannerDevice:didSelectFunctionalUnit:error:"), @didSelectFunctionalUnit(), "v@:@@@")

objc_registerClassPair(delegateClass)

CocoaMessage(0, deviceBrowser, "setDelegate:", class_createInstance(delegateClass, 0))   ;PureBasic app controls the browser
CocoaMessage(0, deviceBrowser, "setBrowsedDeviceTypeMask:", #ICDeviceLocationTypeMaskLocal |
                                                            #ICDeviceLocationTypeMaskRemote |
                                                            #ICDeviceTypeMaskScanner)
CocoaMessage(0, deviceBrowser, "start")

Repeat
  Select WaitWindowEvent()
    Case #PB_Event_CloseWindow
      appQuit = 1
    Case #PB_Event_Gadget
      Select EventGadget()
        Case #scanButton
          scan()
        Case #scannerList
          Select EventType()
            Case #PB_EventType_LeftDoubleClick
              selectedScanner = GetGadgetState(#scannerList) + 1
              activeScanner = scanners(selectedScanner)
              CocoaMessage(0, activeScanner, "requestOpenSession")
          EndSelect
      EndSelect
  EndSelect
Until appQuit = 1

CocoaMessage(0, activeScanner, "release")
CocoaMessage(0, deviceBrowser, "stop")
; CocoaMessage(0, deviceBrowser, "release")
User avatar
TI-994A
Addict
Addict
Posts: 2512
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

Re: Image Scanning on OS-X

Post by TI-994A »

Image

Thank you Shardik - YOU DA MAN!.

It never occurred to me that the issue was with the functional unit all the while. It didn't work when left on default, and it didn't work when the flatbed unit (functional unit 0) was selected. But for some reason, scanning through the document feeder (functional unit 3) works, and so does selecting functional units 2 or 3 (transparency-type units).

Now, with the above settings, the program scans and saves the image to the specified file perfectly, and all is well with the world!

To solve this functional-unit issue, I thought it best to retrieve the available functional units from the device itself, and use the results to make the selection. I tried translating this Objective-C code into PureBasic, but it seems to be returning some arbitrary numbers that don't work:

Code: Select all

NSArray *availableFuncUnits = scanner.availableFunctionalUnitTypes;

Code: Select all

availableFuncUnits = CocoaMessage(0, 0, activeScanner, "availableFunctionalUnitTypes")
availableFuncUnitsSize = CocoaMessage(0, availableFuncUnits, "count")
While i < availableFuncUnitsSize
  funcUnit = CocoaMessage(0, availableFuncUnits, "objectAtIndex:", i)
  Debug funcUnit
  i + 1
Wend
I'm quite sure that this is not the way to do it.

I also tried getting the status information from the didReceiveStatusInformation() method like this, but was unsuccessful:

Code: Select all

statusMsg.s = PeekS(status, -1, #PB_UTF8)
Again, somehow, I'm almost certain that this is bollocks.

And finally, I am still unable to figure out how to format the setMeasurementUnit and setScanArea settings for the functional unit. As you said, it keeps returning an Object is nil error.

If you could kindly steer me in the right direction on these issues, that would pretty much wrap things up here (I hope!)

Once again, my sincere thanks for all your help. :D
Texas Instruments TI-99/4A Home Computer: the first home computer with a 16bit processor, crammed into an 8bit architecture. Great hardware - Poor design - Wonderful BASIC engine. And it could talk too! Please visit my YouTube Channel :D
User avatar
Shardik
Addict
Addict
Posts: 1989
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: Image Scanning on OS-X

Post by Shardik »

In the meantime I have also solved the errors with setting measurement unit and scan area ("Object is nil."). The setting of measurement unit and scan area had to be moved to the callback didSelectFunctionalUnit(). Now the modified code example below works like a charm on both Snow Leopard and Mavericks.

The only quirk remaining is that setting the scan area doesn't seem to have any effect: the resulting image MyScan.png is always a whole page although for testing I even changed the measurement unit to the (for me) more familiar centimeters and a smaller area...

Code: Select all

#ICDeviceLocationTypeMaskLocal     = $00000100
#ICDeviceLocationTypeMaskRemote    = $0000FE00
#ICDeviceTypeMaskScanner           = $00000002
#ICDeviceTypeScanner               = $00000002

ImportC ""
  class_addMethod(Class.i, Selector.i, *Callback, Types.P-ASCII)
  class_createInstance(Class.i, ExtraBytes.i)
  class_addProtocol(Class.i, Protocol.i)
  objc_allocateClassPair(ModelClass.i, NewClassName.P-ASCII, ExtraBytes.i)
  objc_lookUpClass(ClassName.P-ASCII)
  object_setClass(ObjectToModify.i, NewClass.i)
  objc_registerClassPair(NewClass.i)
  objc_getProtocol(ProtocolName.P-ASCII)
  sel_registerName(MethodName.P-ASCII)
EndImport

ImportC "/System/Library/Frameworks/ImageCaptureCore.framework/ImageCaptureCore"
EndImport

ImportC "/System/Library/Frameworks/Quartz.framework/Quartz"
EndImport

Enumeration
  #MainWindow
  #scannerList
  #scanButton
EndEnumeration

Global Dim scanners.i(0)
Global.i delegateClass, activeScanner, selectedScanner, scanning, selectedFunctionalUnit

Procedure scan()
  Debug "initiating scan..."
  scanning = 1
  CocoaMessage(0, activeScanner, "requestScan")   
EndProcedure

ProcedureC deviceDidBecomeReady(object.i, selector.i, device.i)
  Protected FunctionalUnitArray.I
  Protected NumFunctionalUnits.I

  Debug "selected device is ready..."
 
  scanFolder = CocoaMessage(0, 0, "NSURL fileURLWithPath:$", @"/Users/Shardik/Scans/")
  scanFile = CocoaMessage(0, 0, "NSString stringWithString:$", @"MyScan")
 
  ; CocoaMessage(0, device, "requestSelectFunctionalUnit:", 0)   ;0 for flatbed type
  CocoaMessage(0, device, "requestSelectFunctionalUnit:", 3)   ;3 for document feeder type
  CocoaMessage(0, activeScanner, "setTransferMode:", 0)        ;0 for file-mode
  CocoaMessage(0, activeScanner, "setDownloadsDirectory:", scanFolder)
  CocoaMessage(0, activeScanner, "setDocumentName:$", @"MyScan")
  CocoaMessage(0, activeScanner, "setDocumentUTI:$", @"public.png")

  FunctionalUnitArray = CocoaMessage(0, device, "availableFunctionalUnitTypes")

  If FunctionalUnitArray
    NumFunctionalUnits = CocoaMessage(0, FunctionalUnitArray, "count")

    If NumFunctionalUnits
      Debug "Functional unit types:"

      For i = 0 To NumFunctionalUnits - 1
        Select CocoaMessage(0, CocoaMessage(0, FunctionalUnitArray,
          "objectAtIndex:", i), "integerValue")
          Case 0
            Debug "- Flatbed"
          Case 1
            Debug "- Positive transparency"
          Case 2
            Debug "- Negative transparency"
          Case 3
            Debug "- Document feeder"
        EndSelect    
      Next i
    EndIf
  EndIf
EndProcedure

ProcedureC didAddDevice(object.i, selector.i, deviceBrowser.i, addedDevice.i, moreComing.i)
  Debug "device found and added..."
 
  deviceType.i = CocoaMessage(0, addedDevice, "type")   
  deviceName.s = PeekS(CocoaMessage(0, CocoaMessage(0, addedDevice, "name"), "UTF8String"), -1, #PB_UTF8)
 
  If moreComing = #NO And (deviceType & #ICDeviceTypeMaskScanner) = #ICDeviceTypeScanner
    newIndex = ArraySize(scanners()) + 1
    ReDim scanners(newIndex)
    scanners(newIndex) = addedDevice
    AddGadgetItem(#scannerList, -1, deviceName)
    CocoaMessage(0, addedDevice, "setDelegate:",
                 class_createInstance(delegateClass, 0))   ;PureBasic app controls the scanner
  EndIf
EndProcedure

ProcedureC didRemoveDevice(object.i, selector.i, removedDevice.i)
  Debug "device removed"
EndProcedure

ProcedureC didRemoveDeviceInBrowser(object.i, selector.i, deviceBrowser.i, removedDevice.i, moreGoing.i)
  Debug "device removed in browser..."
  CocoaMessage(0, removedDevice, "requestCloseSession")
  CocoaMessage(0, removedDevice, "setDelegate:", 0)
  RemoveGadgetItem(#scannerList, selectedScanner - 1)
  ReDim scanners(ArraySize(scanners()) - 1)

  If activeScanner = removedDevice
    activeScanner = 0
  EndIf
EndProcedure

ProcedureC didOpenSessionWithError(object.i, selector.i, device.i, error.i)
  Debug "session opened... errors: " + Str(error)
EndProcedure

ProcedureC didCloseSessionWithError(object.i, selector.i, device.i, error.i)
  Debug "session closed... errors: " + Str(error)
EndProcedure

ProcedureC didSelectFunctionalUnit(object.i, selector.i, scannerDevice.i, functionalUnit.i, error.i)
  Protected scanArea.NSRect

  If error = 0
    If functionalUnit <> 0
      Debug "functional unit selected: " + functionalUnit
      CocoaMessage(0, functionalUnit, "setMeasurementUnit:", 0)
      Debug "MeasurementUnit set to Inches"

      With scanArea
        \origin\x = 0
        \origin\y = 0
        \size\width = 10
        \size\height = 10
      EndWith
 
      CocoaMessage(0, functionalUnit, "setScanArea:@", @scanArea)
      CocoaMessage(@scanArea, functionalUnit, "scanArea")
      Debug "scanArea set to (" + Str(scanArea\origin\x) + "," +
        Str(scanArea\origin\y) + ")-(" +
        Str(scanArea\origin\x + scanArea\size\width) + "," +
        Str(scanArea\origin\y + scanArea\size\height) + ")"
    EndIf
  Else
    Debug "functional unit selected... errors: " + Str(error)
  EndIf
EndProcedure

ProcedureC didReceiveStatusInformation(object.i, selector.i, device.i, StatusDictionary.i)
  Debug "receiving status info..."

  If StatusDictionary
    Debug PeekS(CocoaMessage(0, CocoaMessage(0, StatusDictionary,
      "valueForKey:$", @"ICLocalizedStatusNotificationKey"), "UTF8String"),
      -1, #PB_UTF8)
  EndIf
EndProcedure

ProcedureC didScanToURL(object.i, selector.i, scannerDevice.i, URL.i, *data) 
  Debug "scanned to " +
    PeekS(CocoaMessage(0, CocoaMessage(0, URL, "absoluteString"), "UTF8String"),
    -1, #PB_UTF8)
  scanning = 0
EndProcedure

ProcedureC didCompleteScanWithError(object.i, selector.i, scannerDevice.i, error.i)
  Debug "scan completed... errors: " + Str(error)
  scanning = 0
EndProcedure
 
ProcedureC didEncounterError(object.i, selector.i, device.i, error.i)
  Debug "encountered error: " + Str(error)
  scanning = 0
EndProcedure

wFlags = #PB_Window_SystemMenu | #PB_Window_ScreenCentered
OpenWindow(#MainWindow, 100, 100, 800, 500, "Scanner Application", wFlags)
ListViewGadget(#scannerList, 0, 0, 200, 500)
ButtonGadget(#scanButton, 220, 10, 100, 30, "SCAN")
SetGadgetColor(#scannerList, #PB_Gadget_BackColor, RGB(200, 200, 255))

deviceBrowser = CocoaMessage(0, 0, "ICDeviceBrowser new")

delegateClass = objc_allocateClassPair(objc_lookUpClass("NSObject"), "PB_Delegate", 0)   ;PureBasic app class

class_addMethod(delegateClass,
                sel_registerName("didRemoveDevice:"), @didRemoveDevice(), "v@:@")
class_addMethod(delegateClass,
                sel_registerName("deviceDidBecomeReady:"), @deviceDidBecomeReady(), "v@:@")
class_addMethod(delegateClass,
                sel_registerName("device:didEncounterError:"), @didEncounterError(), "v@:@@")
class_addMethod(delegateClass,
                sel_registerName("device:didOpenSessionWithError:"), @didOpenSessionWithError(), "v@:@@")
class_addMethod(delegateClass,
                sel_registerName("device:didCloseSessionWithError:"), @didCloseSessionWithError(), "v@:@@")
class_addMethod(delegateClass,
                sel_registerName("device:didReceiveStatusInformation:"), @didReceiveStatusInformation(), "v@:@@")
class_addMethod(delegateClass,
                sel_registerName("deviceBrowser:didAddDevice:moreComing:"), @didAddDevice(), "v@:@@@")
class_addMethod(delegateClass,
                sel_registerName("deviceBrowser:didRemoveDevice:moreGoing:"), @didRemoveDeviceInBrowser(), "v@:@@@")
class_addMethod(delegateClass,
                sel_registerName("scannerDevice:didScanToURL:data:"), @didScanToURL(), "v@:@@@")
class_addMethod(delegateClass,
                sel_registerName("scannerDevice:didCompleteScanWithError:"), @didCompleteScanWithError(), "v@:@@")
class_addMethod(delegateClass,
                sel_registerName("scannerDevice:didSelectFunctionalUnit:error:"), @didSelectFunctionalUnit(), "v@:@@@")

objc_registerClassPair(delegateClass)

CocoaMessage(0, deviceBrowser, "setDelegate:", class_createInstance(delegateClass, 0))   ;PureBasic app controls the browser
CocoaMessage(0, deviceBrowser, "setBrowsedDeviceTypeMask:", #ICDeviceLocationTypeMaskLocal |
                                                            #ICDeviceLocationTypeMaskRemote |
                                                            #ICDeviceTypeMaskScanner)
CocoaMessage(0, deviceBrowser, "start")

Repeat
  Select WaitWindowEvent()
    Case #PB_Event_CloseWindow
      appQuit = 1
    Case #PB_Event_Gadget
      Select EventGadget()
        Case #scanButton
          scan()
        Case #scannerList
          Select EventType()
            Case #PB_EventType_LeftDoubleClick
              selectedScanner = GetGadgetState(#scannerList) + 1
              activeScanner = scanners(selectedScanner)
              CocoaMessage(0, activeScanner, "requestOpenSession")
          EndSelect
      EndSelect
  EndSelect
Until appQuit = 1

If activeScanner
  CocoaMessage(0, activeScanner, "release")
EndIf

CocoaMessage(0, deviceBrowser, "stop")
; CocoaMessage(0, deviceBrowser, "release")
Update 1: I have modified the didReceiveStatusInformation() callback to also display the status which is contained in the status dictionary passed as a parameter to this callback.

Update 2: I have modified the deviceDidBecomeReady() callback to also display the available functional unit types.

Update 3: To prevent an invalid memory access (IMA) when closing the program after the scanner's power button had already been switched off, I have added

Code: Select all

  If activeScanner = removedDevice
    activeScanner = 0
  EndIf
in procedure didRemoveDeviceInBrowser() and changed

Code: Select all

CocoaMessage(0, activeScanner, "release")
to

Code: Select all

If activeScanner
  CocoaMessage(0, activeScanner, "release")
EndIf
at the end of the code example. I have tested this code successfully with PB 5.44 x86 in both ASCII and Unicode mode on MacOS 10.6.8 (Snow Leopard) and with PB 5.44 x86 and x64 in both ASCII and Unicode mode on MacOS 10.7.5 (Lion).
Last edited by Shardik on Wed Dec 28, 2016 9:30 pm, edited 5 times in total.
User avatar
TI-994A
Addict
Addict
Posts: 2512
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

Re: Image Scanning on OS-X

Post by TI-994A »

Image
Shardik wrote:...I have also solved the errors with setting measurement unit and scan area ("Object is nil."). The setting of measurement unit and scan area had to be moved to the callback didSelectFunctionalUnit()...
You're a true genius. But more importantly, how did you figure that out? I didn't find that anywhere in the documentation or in any of the tonnes of examples that I had pored over.
Shardik wrote:The only quirk remaining is that setting the scan area doesn't seem to have any effect: the resulting image MyScan.png is always a whole page although...
No; it works! I've tested it with both inches and centimetres settings, and it scans precisely the given area. Amazing!

The reason that you're not getting it is because the Brother ADS-2100 that you're using is a document-feeder type, which ignores the scan area setting.

This is wonderful! We're really getting this thing to work.

I bow to you, GURU.
Image

But I still await updates on reading the availableFunctionalUnitTypes property array, as well as the status message returned by the didReceiveStatusInformation() method. :lol:
Texas Instruments TI-99/4A Home Computer: the first home computer with a 16bit processor, crammed into an 8bit architecture. Great hardware - Poor design - Wonderful BASIC engine. And it could talk too! Please visit my YouTube Channel :D
User avatar
Shardik
Addict
Addict
Posts: 1989
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: Image Scanning on OS-X

Post by Shardik »

TI-994A,

thank you very much for your kind words. But unfortunately you are massively exaggerating about my capabilities. All PB Mac programmers can't be grateful enough to Wilbert who is a true genius, a Cocoa framework magician and assembler guru. Without Wilbert's incredible work and Fred's help on the former oMsg() library which finally became the native CocoaMessage() function and the multitude of Wilbert's excellent code examples my help for your scanning program would not have been possible. And I bet that if Wilbert would have had a proper scanner at home, he would have had worked out a scanning solution for you in minutes while I needed many hours... :lol:
TI-994A wrote:The reason that you're not getting it is because the Brother ADS-2100 that you're using is a document-feeder type, which ignores the scan area setting.
That's another proof of my silliness... :oops:
Of course it's technically not possible to use a sheet feeder type scanner for scanning only a given area...
TI-994A wrote:But I still await updates on reading the availableFunctionalUnitTypes property array, as well as the status message returned by the didReceiveStatusInformation() method. :lol:
I have modified the didReceiveStatusInformation() callback in my last code posting to also display the status contained in the status dictionary and the deviceDidBecomeReady() callback to report the available functional unit types. :wink:
User avatar
TI-994A
Addict
Addict
Posts: 2512
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

Re: Image Scanning on OS-X

Post by TI-994A »

SHARDIK! YOU DID IT MAN!

Image
Shardik wrote:...you are massively exaggerating about my capabilities. All PB Mac programmers can't be grateful enough to Wilbert who is a true genius ... and the multitude of Wilbert's excellent code examples...
Yes; I too have studied his many code examples intently, and yours too, and would not have imagined to approach this topic without them. Like you said, if only he had a scanner.

But you went out of your way to help me, and to borrow a quote from John McClane (Bruce Willis, Die Hard 4.0), "That's what makes you that guy."

Bottom-line: I can't thank you enough. Danke Vielmals!

Just FYI, in the didSelectFunctionalUnit() method, I was able to get the selected functional unit type with this CocoaMessage():

Code: Select all

CocoaMessage(0, functionalUnit, "type")
Shardik wrote:...the modified code example below works like a charm on both Snow Leopard and Mavericks.
It should also be noted that all my tests were done on Mac OSX Lion v10.7.5. So, it clearly works on Snow Leopard, Lion, and Mavericks :D

I truly believe that this is one for the PureBasic books, and warrants an addition into the Mac OSX Tricks 'n' Tips section. Please do the honours, Shardik; this is definitely your baby. :wink:

Thanks again, and hope you have a wonderful weekend. I know I will! :lol:
Texas Instruments TI-99/4A Home Computer: the first home computer with a 16bit processor, crammed into an 8bit architecture. Great hardware - Poor design - Wonderful BASIC engine. And it could talk too! Please visit my YouTube Channel :D
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Image Scanning on OS-X

Post by wilbert »

TI-994A wrote:I truly believe that this is one for the PureBasic books, and warrants an addition into the Mac OSX Tricks 'n' Tips section.
I added a link to the source in the first post of that thread :wink:
It's indeed a very nice contribution :D
Windows (x64)
Raspberry Pi OS (Arm64)
User avatar
TI-994A
Addict
Addict
Posts: 2512
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

Re: Image Scanning on OS-X

Post by TI-994A »

wilbert wrote:I added a link to the source in the first post of that thread :wink:
It's indeed a very nice contribution :D
Hi wilbert. I just saw the new link in the Mac OSX Tricks 'n' Tips section; a very nice contribution indeed. Thank you.

And like Shardik said, it wouldn't have been possible without your contributions. I'd have to say that the techniques implemented in this example were most certainly derived from the many API examples that you, and the other OSX gurus, had posted in these forums over the years. Truly educational stuff.

So, dank u wel. :D

I just realised that it took 36 days to get this thing off the ground. But it is well worth every minute! :lol:
Texas Instruments TI-99/4A Home Computer: the first home computer with a 16bit processor, crammed into an 8bit architecture. Great hardware - Poor design - Wonderful BASIC engine. And it could talk too! Please visit my YouTube Channel :D
Post Reply