Yakuza 3 played by real Yakuza!

This is awesome, an american reporter (I think) who worked in Japan for years covering the tokyo underground managed to persuade 3 real Yakuza to play Sega’s awesome Yakuza 3. The first 2 were good and the fourth is very good. The third has recently come out in the west. I’ve been playing it, it’s ace. 😛

Yakuza 3, reviewed by yakuza

It’s surprising and witty.

Posted in Art | Comments Off on Yakuza 3 played by real Yakuza!

Gym diary. Part 1 of X

Well today was a first chance for a free personal trainer less session at the new gym. It broke down like this:

2km rowing at 7 in 9 mins~
20 reps shoulder press at 8
1km jogging at speed 9 in 7mins~
30 reps 4kg weights laying on back.
20 reps chest press at 6
10 reps leg curl at 9
Stretching exercises.

Not awful for a casual session I hope. Rowing should be <8 mins.

Posted in Exercise | Tagged | Comments Off on Gym diary. Part 1 of X

A tiny tiny CoreData revelation.

Following on from the previous discovery of all the useful debugging features, including the Build & Analyze feature and the Performance Tool stuff, the argument to debug core data has one problem. It doesn’t display your parameters. So change that 1 up a bunch of times (I have it set to 5) and it becomes more useful. This led me to realise why my app crashed when making lots of edits/deletes of categories (with some rules to other data cascading deletes and some just nullifying the relevant columns. It’s all down to caching and produces this excellent error message, no sarcasm this is a fantastic error message:

2010-08-09 18:52:37.125 CC [4551:207] FATAL ERROR: The persistent cache of section information does not match the current configuration.  You have illegally mutated the NSFetchedResultsController's fetch request, its predicate, or its sort descriptor without either disabling caching or using +deleteCacheWithName:

As I jump into various sections I was setting the cache up with the name of the section. Of course depending on my search criteria the predicate (the WHERE bit of the SQL in Core Data parlance) changes. So now I build the names of the caches a bit more dynamically:

NSString *cacheName = [NSString stringWithFormat:@"TitlesIn%@",category.name]

This keeps everything really smooth when hopping around at the expense of a bit more memory usage. It’s easy enough to sort out the memory issue as well if necessary as the error message tells us how to delete caches so we could implement code to keep an eye on how often caches are hit and delete the ones that are old.

Sorted.

Posted in Programming | Comments Off on A tiny tiny CoreData revelation.

More Objective-C cocoa gotchas.

Firstly… when you’re chopping and changing lots of files in and out (and using the refactoring functions to rename classes by highlighting the name and hitting apple-option-j) you can get linking errors of all sorts afterwards and strange crashes saying that NIB files could not be loaded despite them clearly being in your project. In this instance look under Targets->Project Name and Copy Bundle Resources to ensure the NIB files are present and Compile Sources to ensure your souce code is present (not headers). You can find they are missing so despite being in the project they are not part of the compilation process. Simply drag them back in.

Also this article is fantastic for debugging EXC_BAD_ACCESS errors:

Debbuging Autorelease

For example, this is awesome:

Cooking Companion(38689,0xa0366500) malloc: *** error for object 0x6669134: incorrect checksum for freed object - object was probably modified after being freed.
*** set a breakpoint in malloc_error_break to debug
Program received signal: “SIGABRT”.
Data Formatters temporarily unavailable, will re-try after a 'continue'. (Not safe to call dlopen at this time.)
(gdb) shell malloc_debug 38689 0x6669134
bash: malloc_debug: command not found
(gdb) shell malloc_history 38689 0x6669134
malloc_history Report Version: 2.0
Process: Cooking Companion [38689]
Path: /Users/diziet/Library/Application Support/iPhone Simulator/4.1/Applications/12953D32-17B6-4E16-BE10-4AED1385C675/Cooking Companion.app/Cooking Companion
Load Address: 0x1000
Identifier: Cooking Companion
Version: ??? (???)
Code Type: X86 (Native)
Parent Process: gdb-i386-apple-darwin [38691]

Date/Time: 2010-08-08 18:34:08.597 +0100
OS Version: Mac OS X 10.6.4 (10F569)
Report Version: 6

ALLOC 0x6669130-0x6669177 [size=72]: thread_a0366500 |start | main | UIApplicationMain | GSEventRun | GSEventRunModal | CFRunLoopRunInMode | CFRunLoopRunSpecific | __CFRunLoopRun | __CFRunLoopDoObservers | __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ | CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) | CA::Transaction::commit() | CA::Context::commit_transaction(CA::Transaction*) | CALayerDisplayIfNeeded | -[CALayer display] | -[UIGroupTableViewCellBackground displayLayer:] | -[UIGroupTableViewCellBackground(UIGroupTableViewCellBackgroundInternal) _cachedImageForKey:] | doClip | CGGStateClipToPath | maybeCopyClipState | CGClipStackCreateMutableCopy | calloc | malloc_zone_calloc
----
FREE 0x6669130-0x6669177 [size=72]: thread_a0366500 |start | main | UIApplicationMain | GSEventRun | GSEventRunModal | CFRunLoopRunInMode | CFRunLoopRunSpecific | __CFRunLoopRun | __CFRunLoopDoObservers | __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ | CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) | CA::Transaction::commit() | CA::Context::commit_transaction(CA::Transaction*) | CALayerDisplayIfNeeded | -[CALayer display] | -[UIGroupTableViewCellBackground displayLayer:] | -[UIGroupTableViewCellBackground(UIGroupTableViewCellBackgroundInternal) _cachedImageForKey:] | CGContextRestoreGState | CGGStackRestore | CGGStateRelease | free

							
Posted in Programming | Comments Off on More Objective-C cocoa gotchas.

CoreData and to-many relationships.

I’ve had a problem with a category being able to reference many subjects but a subject only having one category. Kept getting a fault for the relationship. The error looked like this in the debugger:

CoreData: annotation: to-many relationship fault

This apparently is the solution, didn’t notice this in the main Core Data documentation:

——-
It is important to understand the difference between the values returned by the dot accessor and by mutableSetValueForKey:. mutableSetValueForKey: returns a mutable proxy object. If you mutate its contents, it will emit the appropriate key-value observing (KVO) change notifications for the relationship. The dot accessor simply returns a set. If you manipulate the set as shown in this code fragment:

[aDepartment.employees addObject:newEmployee]; // do not do this!
then KVO change notifications are not emitted and the inverse relationship is not updated correctly.

Recall that the dot simply invokes the accessor method, so for the same reasons:

[[aDepartment employees] addObject:newEmployee]; // do not do this, either!
——-

Hmm…. It would have been so much quicker for me to use SQLLite directly and issue my own SQL statements to manage the data as that’s what I’m used to. However I feel I should do it the ‘apple’ (e.g. slightly mental) way.

**Footnote: I’m not doing that. Ah well. I must be doing something like it. **

***Update:  It’s probably this, apple hides relationships from you under faults. If it didn’t it might follow all the relationships for one request and run out of memory. ***

Posted in Programming | Comments Off on CoreData and to-many relationships.

I need this for when I’m painting.

Perhaps I could get it attached to my future cyborg arm?

http://research.microsoft.com/en-us/um/redmond/groups/ivm/imudeblurring/

Posted in Miscellany | Comments Off on I need this for when I’m painting.

Crypto chall…. errr whut?

Thanks to kirrus. I hadn’t actually looked at it since saving it to a text file… but yes it does indeed have exif data.

You win!

diziet@xenoglossicist:~$ cat cs_cypher.txt | base64 -d > fish.bin
diziet@xenoglossicist:~$ file fish.bin
fish.bin: JPEG image data, JFIF standard 1.01
diziet@xenoglossicist:~$ logout
Connection to thelostsouls.org.uk closed.
Paul-Downss-MacBook-Pro:Documents diziet$ scp thelostsouls.org.uk:fish.bin fish.jpg
diziet@thelostsouls.org.uk’s password:
fish.bin 100% 20KB 19.9KB/s 00:00
Paul-Downss-MacBook-Pro:Documents diziet$

It had to be steganograhy or something as well right?

Or not….

diziet@xenoglossicist:~/crypto/stegdetect-0.6$ ./stegdetect ../fish.jpg
../fish.jpg : negative

Posted in Miscellany | Comments Off on Crypto chall…. errr whut?

Regular expression that checks for primes.

This via

Noulakaz

It might well be, but it’s a hack as it bombs at larger numbers:

irb(main):001:0> def isp(n)
irb(main):002:1> (“1” * n) !~ /^1?$|^(11+?)\1+$/
irb(main):003:1> end
=> nil
irb(main):004:0> isp(55)
=> false
irb(main):005:0> isp(57)
=> false
irb(main):006:0> isp(101)
=> true
irb(main):007:0> isp(100000001)
RegexpError: Stack overflow in regexp matcher: /^1?$|^(11+?)\1+$/
from (irb):2:in `isp’
from (irb):7
from :0
irb(main):001:0> def isp(n)irb(main):002:1> (“1” * n) !~ /^1?$|^(11+?)\1+$/irb(main):003:1> end=> nilirb(main):004:0> isp(55)=> falseirb(main):005:0> isp(57)=> falseirb(main):006:0> isp(101)=> trueirb(main):007:0> isp(100000001)RegexpError: Stack overflow in regexp matcher: /^1?$|^(11+?)\1+$/        from (irb):2:in `isp’        from (irb):7        from :0

Yes regular expressions are cool and all but they are hacks.

Posted in Programming | Comments Off on Regular expression that checks for primes.

How to populate CoreData at runtime from a remote source.

To me it seems that documentation on populating apple Core Data based stores is sorely lacking instructions on how to populate the database at runtime from remote sources. Most people electing to populate an SQL Lite database and then just copy it in when the app is first loaded.  UGH.  I wanted to retrieve the initial database at runtime and then also use this remote store to update my locally held database at the users request; merging the data intelligently.  The problem was trying to do it as the app launches and trying to do it at various points in the App Delegate or view controller startup screwed with stuff and caused EXC_BAD_ACCESS.  Which implies something somewhere got de-allocated/allocated when it shouldn’t have.  Here is the solution, the remote store is a plist or PropertyList, which is an xml format used a lot by apple and apple software.

I’ll assume a lot of prior knowledge here unless people comment saying they want more information.

Start a new project using core data, in my case it’s a universal iPhone Navigation Bar project.  Within this project I deleted the default ‘Event’ database stuff it set up and created a database and relationships of my own.  In this example I only use one table called ‘Category’ which holds nothing but ‘name’.  A custom cell was created to display this information which also has a UIImageView field loading a local image based on the name of the category.  References in Root View Controller were changed from ‘Event’ to ‘Category’ and from ‘timestamp’ to ‘name’.   Ignoring the code to display the custom cell itself it necessitated edits to:

- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
- (NSFetchedResultsController *)fetchedResultsController

I’m not allowing editing here so insertNewObject is gone, but if you were you’d have to edit that to so you can move away from the example data.

The problem for me was not being able to ascertain where best to shove the following code to populate the database:


	// Read in property list
	NSPropertyListFormat *format;
	NSString *errorDesc;
	NSData *plistXML = [[NSData alloc] initWithContentsOfURL:source];
 
	NSDictionary *data = (NSDictionary *)[NSPropertyListSerialization propertyListFromData:plistXML 
																				   mutabilityOption:NSPropertyListImmutable format:format errorDescription:&errorDesc];
	
	NSMutableSet *scategories = [[NSMutableSet alloc] init];
 
	// Get all category names into mutable array
	for(id object in data) {
		for(id key in object) {
			if([key isEqualToString:@"type"])
				[scategories addObject:(NSString *)[object objectForKey:key]];
		}
	}
 
	NSMutableArray *categories = [[NSMutableArray alloc] initWithSet:scategories];
	[scategories release];
	[categories sortUsingSelector:@selector(compare:)];
 
	// Create fetch request to get all category names
	NSFetchRequest *categoriesRequest = [[[NSFetchRequest alloc] init] autorelease];
	[categoriesRequest setEntity:[NSEntityDescription entityForName:@"Category" inManagedObjectContext:managedObjectContext_]];
	[categoriesRequest setPredicate:[NSPredicate predicateWithFormat:@"(name in %@)",categories]];
 
	// Sort our data store categories too like
	[categoriesRequest setSortDescriptors:[NSArray arrayWithObject:[[[NSSortDescriptor alloc] 
																	 initWithKey:@"name" ascending:YES] autorelease]]];
 
	// Execute fetch
	NSError *error;
	NSArray *categoriesMatchingNames = [managedObjectContext_ executeFetchRequest:categoriesRequest error:&error];
 
	// We need to enumarate the categoriesMatchingNames array as we must control when next to fetch an object
	NSEnumerator *enumurateDSCategories = [categoriesMatchingNames objectEnumerator];
 
	// Get the names to parse
	for(NSString *type in categories) {
		Category *dsCategory = (Category *)[enumurateDSCategories nextObject];
		if( (dsCategory == nil) && ![type isEqualToString:[dsCategory name]] ) {
			Category *category = (Category *)[NSEntityDescription insertNewObjectForEntityForName:@"Category" inManagedObjectContext:managedObjectContext_];
			[category setName:type];
		}
	}
 
	if(![managedObjectContext_ save:&error])
	{	
	 NSLog(@"Core data failed to save new objects!");
	}
	
	[categories release];
	[plistXML release];
	
	return TRUE;

I tried placing this in:


- (NSPersistentStoreCoordinator *)persistentStoreCoordinator 

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

and viewDidLoad, viewDidAppear and others within the Root View Controller. None worked, they populated the database but then crashed. I finally decided that the best approach would be to argue that if I had no categories defined the other data isn’t there either. To begin with I settled on a modal dialogue but your approach could vary. In the App Delegate I defined two functions, one to do the actual work later on, not yet implemented except for Category, and the other to use a local xml file initially anyway:


- (BOOL)importData {
	NSString *filePath = [[NSBundle mainBundle] pathForResource:@"dl" ofType:@"xml"];  
	NSURL *localfile = [[NSURL alloc] initFileURLWithPath:filePath];
	[self mergeDataFromExternalSource:localfile];
	return TRUE;
}

- (BOOL)mergeDataFromExternalSource:(NSURL *)source {
	// Read in property list
	NSPropertyListFormat *format;
	NSString *errorDesc;
	NSData *plistXML = [[NSData alloc] initWithContentsOfURL:source];
 
	// Get entire XML file into an NSDictionary object which is ideal for plist data
	NSDictionary *data = (NSDictionary *)[NSPropertyListSerialization propertyListFromData:plistXML 
							   mutabilityOption:NSPropertyListImmutable format:format errorDescription:&errorDesc];
	
	// Use NSMutableSet as we are parsing the plist and we only want one instance of each category
	// A set does that for us unlike an array.
	NSMutableSet *scategories = [[NSMutableSet alloc] init];
 
	// Get all category names into mutable array
	for(id object in data) {
		for(id key in object) {
			if([key isEqualToString:@"type"])
				[scategories addObject:(NSString *)[object objectForKey:key]];
		}
	}
 
	// However we cannot sort sets so we now change it into an array anyway
	NSMutableArray *categories = [[NSMutableArray alloc] initWithSet:scategories];
	[scategories release];
	[categories sortUsingSelector:@selector(compare:)];
 
	// Create fetch request to get all category names
	NSFetchRequest *categoriesRequest = [[[NSFetchRequest alloc] init] autorelease];
	[categoriesRequest setEntity:[NSEntityDescription entityForName:@"Category" inManagedObjectContext:managedObjectContext_]];
	// Only fetch records that exist in our input data
	[categoriesRequest setPredicate:[NSPredicate predicateWithFormat:@"(name in %@)",categories]];
 
	// Sort our data store categories too so they match our input data.
	[categoriesRequest setSortDescriptors:[NSArray arrayWithObject:[[[NSSortDescriptor alloc] 
												 initWithKey:@"name" ascending:YES] autorelease]]];
 
	// Execute fetch
	NSError *error;
	NSArray *categoriesMatchingNames = [managedObjectContext_ executeFetchRequest:categoriesRequest error:&error];
 
	// We need to enumarate the categoriesMatchingNames array as we must control when next to fetch an object
	// We only want to fetch the next object when it matches a category as we have n input categories but potential n-x 
	// stored ones.
	NSEnumerator *enumurateDSCategories = [categoriesMatchingNames objectEnumerator];
 
	// Get an initial category
	Category *dsCategory = (Category *)[enumateDSCategories nextObject];
	// Get the names to parse
	for(NSString *type in categories) {
		// If the data store category is blank (we had none or run out) or they don't match add it.
		if( (dsCategory == nil) || ![type isEqualToString:[dsCategory name]] ) {
			Category *category = (Category *)[NSEntityDescription insertNewObjectForEntityForName:@"Category" inManagedObjectContext:managedObjectContext_];
			[category setName:type];
		}
		// If the data store category matches the input one get the next data store category
		if( [type isEqualToString:[dsCategory name]] ) {
			Category *dsCategory = (Category *)[enumurateDSCategories nextObject];
		}
	}
 
	if(![managedObjectContext_ save:&error])
	{	
	 NSLog(@"Core data failed to save new objects!");
	}
	
	[categories release];
	[plistXML release];
	
	return TRUE;
}

in the Root View Controller source file (RootViewController.m) the following is modified/added:


- (void)viewDidAppear:(BOOL)animated {
	id  sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:0];
	if ( [sectionInfo numberOfObjects] < 1) {
		UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"No Data." 
														message:@"Initial data not found, loading defaults.\nHit cancel  and quit to configure a remote source in Settings.app."
													   delegate:self
											  cancelButtonTitle:@"Cancel" 
											  otherButtonTitles:nil];
		[alert addButtonWithTitle:@"OK"];
		[alert show];
		[alert release];
	}
	
    [super viewDidAppear:animated];
}

- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
	if ( buttonIndex == 1 )
		[((Delicious_LibraryAppDelegate *)[[UIApplication sharedApplication] delegate]) importData];
}

The above counts how many rows I have in my main table (which has one column, hence objectAtIndex is 0) and if it is less than 1 I assume we have no data and prompt the user. When the user hits OK the importData function is called. For now it just loads the local data but it could check the app settings and use the remote function.

Posted in Programming | Tagged , , , , | 2 Comments

A little bit of this and that.

Quite a few things have vied for my attention this week. I ploughed through 2 books, namely The Girl who Played With Fire a book on Obj-C. I’m now reading The Girl who Kicked the Hornets Next and Living Next Door to the God of Love (which I feel obliged to read to complete the authors set). Dragon Age has lured me back in and Awakenings was duly completed, then Darkspawn Chronicles fell and Leliana’s Song was started, which I hope is good as she ended up being my main’s partner in the first part. This made me think. What game has vied the most for my time? I could only think of two. Dragon Age and Fallout 3. I know Mass Effect 2 is probably a distant 3rd along with some close run compadres. It turns out that Dragon Age, combining play times for all expansions thus far + alts, comes to 127 hours. With Leliana’s song to go. Fallout 3 combined play time according to Steam is 105 hours. Bloody Hell!

In other news the iPhone project has come along reasonably well. I’ve rounded off all three panes of the UI (Add New, View Saved, Settings) and started on the custom views required for each type of task under Saved. I’ve found an expansion to opencv called cvblobslib which looks interesting although an initial run against a picture using the iphone 3G’s camera comsumed 3GB of ram in the emulator. Way outside of the 40MB Max (during opencv) and 2-3MB in standard use target I have for the iPhone app. Aside from that I realised again that with regards to computer vision less is so very very much more. To this end I re-jigged how I operate. Regardless of whether I land on a best method of blobs or contours I tweaked my original process from:

  1. Smooth
  2. Flatten
  3. Threshold
  4. Find Contours
  5. Draw Largest

To this:

  1. Create a copy of the image scaled down to 1024 if image is larger
  2. Keep a copy of the scaling factor required
  3. Smooth
  4. Flatten
  5. Threshold
  6. Find Contours or Find blobs
  7. Draw Largest scaling up discovered co-ordinates if found

This has several benefits, the two obvious ones being it uses far less ram and it’s much much faster for the same result. As a result the blob method becomes viable. The less obvious benefit is it’s actually more accurate. With higher resolution photographs the grain of the paper and reflections etc. all add detail to the image that clouds what you are trying to ‘see’. Thus scaling down, much like the smoothing, discards this unnecessary detail. By working on a copy when I need to access higher quality information (say for OCR or letting the user highlight an area to save for future direct reference in the case of complex text or image based clues) I can simply scale up the co-ordinates and pluck out the original data.

Alongside this I’ve got the hang of using the Instruments tool provided with XCode to track down memory leaks and the app is now 100% leak free. Which should save time later before it gets even MORE complicated.

Also Hough Transforms. I hope. Please god work even though I do not understand you fully.

Patrick in the comments asked for a bit more info on using cvblobs, so here it is:

Ok here’s what I hope are the relevant bits. Under build settings and ‘Other Linker Flags’ I have set:

-lstdc++ -lz $(SRCROOT)/opencv_simulator/lib/libcv.a $(SRCROOT)/opencv_simulator/lib/libcxcore.a $(SRCROOT)/opencv_simulator/lib/libcvaux.a

Where opencv_simulator is a directory containing my OpenCV libs built for the simulator, e.g. debug on and built for i386 and not arm. libcvaux.a is the file containing the cvblobs stuff.

The code snippet is:

			/* Use opencv expansion library for BLOB detection */
			CBlobResult blobs;
			CBlob *currentBlob;
			CBlob biggestBlob;
			
			// find non-white blobs in thresholded image
			blobs = CBlobResult( working, NULL, 0 );
			// exclude the ones smaller than last value in func.
			blobs.Filter( blobs, B_EXCLUDE, CBlobGetArea(), B_LESS, 10000 );
			
			// Having filtered above to get rid of the minnows get the big boy.
			blobs.GetNthBlob( CBlobGetArea(), 0, biggestBlob );
			
			// Create image for display
			temp = cvCreateImage(cvGetSize(working), IPL_DEPTH_8U, 4);
			// display filtered blobs
			cvMerge( working, working, working, NULL, temp );
			swapIplImage(&temp, &working);
			cvReleaseImage(&temp);
			
			for (int i = 0; i < blobs.GetNumBlobs(); i++ )
			{
				currentBlob = blobs.GetBlob(i);
				currentBlob->FillBlob( working, CV_RGB(255,0,0));
			}
			
			biggestBlob.FillBlob( working, CV_RGB(0,0,255) );
			
			// Adding all the contours to the animation
			display = [self UIImageFromIplImage:working];
			if(display) {
				steps++;
				[animation addObject:display];
				self.image = display;
			}
			
			cvReleaseImage(&working);

In the code above the variables are:

IplImage *temp, working;
UIImage *display;
NSMutableArray *animation;

In the code the variable working contains the initial input, which at that point has had the following operations performed already:

– Scaled to a lower resolution maintaining aspect ratio.
– Gaussian smoothed n times.
– Flattened to 1 channel (2 colours), e.g. IPL_DEPTH_8U as required by a lot of the cv functions.
– Adaptive Threshold applied with cvAdaptiveThreshold.

The final steps are involved in displaying some of the output back to the iPhones display for me, so I can see what blobs it’s detected etc. Hence the use of a NSMutableArray of UIImages which I go on to make an animation in a UIImageView. The use of cvMerge, IIRC, is to convert the 8U image back into a full image by simply merging it three times (by passing in the working image as each channel).

Don’t take any of the above actions as gospel, the only real requirement is to have an IPL_DEPTH_8U image which is what the functions require as input. I do the above to clear out noise, but my success was limited and I moved onto another project. Although I will revisit this in the future.

It’s worth noting here that the whole project was experimental for me, and in the settings for my app I had a switch that flipped between cvblobs and the standard cvFindContours routines. The cvBlobs code was far cleaner and shorter, however I never really settled on either as being better… in part because a lot of my input data was poor. E.g. noisy due to light conditions and the limitations of the iphones camera. I eventually found that reducing the size of the images and a certain amount of blurring helped but I never got it working as consistently as the Sudoku Grab app that’s in the store and it was a lot slower to process images.

HTH

Posted in Literature, Miscellany, Programming | Tagged , , , , | 6 Comments