Wednesday, 17 July 2019

Get Raspberry Pi board details from Raspbian

I have a Raspberry Pi that I didn't use regularly. I brushed off the dust and wanted to play around with the hardware again but I wasn't sure of the pin configuration. I then came across this handy utility that displays a summary of the Raspberry Pi hardware.
 The pinout command gives this summary.


This is what the output looks like. From this you can see that the Raspberry Pi Model is 3B V1.2

Saturday, 15 June 2019

org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter [java.lang.String arg0] in method [void ParametrizedTest.test(java.lang.String)].

I was experimenting with JUnit 5 and Parameterized Tests and hit the following error.

org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter [java.lang.String arg0] in method [void ParametrizedTest.test(java.lang.String)].

This was the code that was used to trigger the error.


import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertTrue;

public class ParametrizedTest {

    @Test
    @ParameterizedTest
    @MethodSource("stringProvider")
    void test(String string) {
        assertTrue(true);
    }

    public static Stream stringProvider() {
        return Stream.of("", null, "123");
    }
}

The issue is that the test method is annotated with both @Test and @ParameterizedTest. When using parameterized tests, you should only annotate your test method with @ParameterizedTest and not @Test. Removing the @Test annotation will prevent this error.


import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertTrue;

public class ParametrizedTest {

    @ParameterizedTest
    @MethodSource("stringProvider")
    void test(String string) {
        assertTrue(true);
    }

    public static Stream stringProvider() {
        return Stream.of("", null, "123");
    }
}

Saturday, 7 October 2017

Unity3d Change Costume of a 2d Character

In this tutorial we will look at re-skining a 2D character and applying a costume to them in Unity.

Here's what the main character looks like without any costume on.


The player can then change the costumes of the main character. Here's an example of main character re-skinned with different costumes.




The player is split into different sprites. There is a sprite sheet for common player sprites for all costumes and a sprite sheet for each of the different parts of the character that can be re-skinned. This is what the main sprite sheet looks like.


There are also other sprite sheets for the Hats, shoes and facial hair. These are all stored in the Assets/Resources/Skins/Player directory.



The player then has different GameObjects for different areas to be skinned. In this case it is a Hat, Facial Hair and Shoes. The hierarchy can be seen here for the player. 


Each of these components are just an empty GameObject with just a SpriteRenderer component.


You can see from the image from the inspector above that these don't have a sprite assigned to them. We are going to add a script to do this at runtime. 

The script expects the sprite sheet to have been split up into sprites using the Sprite Editor. In the image below you can see that the different sprite sheets are split up into Elf, Leprechaun, Pirate, Santa, Viking sprites. This naming pattern has to be followed for other sprite sheets for the below code to work. 


The following script does this by checking if a costume change is needed in the Update method, if it does the ChangeCostume method is called.

The ChangeCostume method works by loading in the facialHair.png, hats.png and shoes.png sprite sheets and stores them into sprite arrays. It gets the lists of SpriteRenderers to set. It also gets the sprite that the SpriteRenderer is going to be set to and sets it.

For example the following code loads in the sprite sheet Assets/Resources/skins/player/hats.png. The elements of the array are the Sprites in the image above. The renderers array contains all the SpriteRenderer components in the Player GameObject. We are only interested in the "Hat" SpriteRenderer so we call the GetSpriteRenderersWithName method and pass in "Hat". We then get the target sprite to set in the SpriteRenderer and we set it.

Sprite[] hatSprites = Resources.LoadAll ("skins/player/hats");
SpriteRenderer[] renderers = GetComponentsInChildren ();
List<SpriteRenderer> hatRenderersToSet = GetSpriteRenderersWithName ("Hat", renderers);

Sprite hatSprite = GetSpriteWithName (costumeName, hatSprites);
CustomisePlayer (hatSprite, hatRenderersToSet, costumeName);

Here's the entire code for the class.
 
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Reskin : MonoBehaviour {

 public string costumeName;

 string currentCostume;

 void Start () {
  currentCostume = costumeName;
  ChangeCostume (currentCostume);
 }
 
 void Update () {
  //only change the costume if the costume has to be changed
  if (costumeName != currentCostume) {
   ChangeCostume (costumeName);
   currentCostume = costumeName;
  }
 }

 public void ChangeCostume(string costumeName) {
  Sprite[] facialHairSprites = Resources.LoadAll<Sprite> ("skins/player/facialHair");
  Sprite[] shoesSprites = Resources.LoadAll<Sprite> ("skins/player/shoes");
  Sprite[] hatSprites = Resources.LoadAll<Sprite> ("skins/player/hats");

  SpriteRenderer[] renderers = GetComponentsInChildren<SpriteRenderer> ();

  List<SpriteRenderer> hatRenderersToSet = GetSpriteRenderersWithName ("Hat", renderers);
  List<SpriteRenderer> facialHairRenderersToSet = GetSpriteRenderersWithName ("FacialHair", renderers);
  List<SpriteRenderer> shoeRenderersToSet = GetSpriteRenderersWithName ("Shoe", renderers);


  Sprite facialHairSprite = GetSpriteWithName (costumeName, facialHairSprites);
  Sprite hatSprite = GetSpriteWithName (costumeName, hatSprites);
  Sprite shoeSprite = GetSpriteWithName (costumeName, shoesSprites);

  CustomisePlayer (hatSprite, hatRenderersToSet, costumeName);
  CustomisePlayer (facialHairSprite, facialHairRenderersToSet, costumeName);
  CustomisePlayer (shoeSprite, shoeRenderersToSet, costumeName);
 }

 /***
  * Loop through the SpriteRenderers and set the correct sprite for them
  */
 public void CustomisePlayer(Sprite sprite, List<SpriteRenderer> renderers, string costumeName) {

  foreach (SpriteRenderer renderer in renderers) {
   renderer.sprite = sprite;
  }
 }

 /***
  * Convenience method to get the Sprite with a name.
  */
 Sprite GetSpriteWithName(string spriteName, Sprite[] sprites) {
  foreach (Sprite sprite in sprites) {
   if (sprite.name == costumeName) {
    return sprite;
   }
  }
  return null;
 }

 /***
  * Get a list of all the SpriteRenderers with a specific name
  */
 List<SpriteRenderer> GetSpriteRenderersWithName(string rendererName, SpriteRenderer[] renderers) {
  List<SpriteRenderer> spriteRenderers = new List<SpriteRenderer> ();
  foreach (SpriteRenderer renderer in renderers) {
   if (renderer.name == rendererName) {
    spriteRenderers.Add (renderer);
   }
  }
  return spriteRenderers;
 }
}


The costumes can be seen in this video at 0:39




Stop the Pop can be downloaded on The App Store: https://itunes.apple.com/us/app/stop-the-pop/id1166315634?ls=1&mt=8
Google Play: https://play.google.com/store/apps/details?id=com.stopthepopgame.stopthepop

Monday, 25 September 2017

Iterate Over Lists in Kotlin

Create a list, add, remove and then iterate over a list.

fun main(args: Array<String>) {
    var list = mutableListOf<String>("zero", "one", "two", "three", "four")

    list.add("five")
    list.remove("zero")

    for(str in list) {
        println("value = " + str)
    }
}


This has the following output

value = one
value = two
value = three
value = four
value = five

Iterate Over an Array in Kotlin

In this example we create an array, called array. The contents of the array are created by using the Kotlin arrayOf function. To iterate over the array, we use a for loop.

fun main(args: Array<String>) {
    var array = arrayOf("one", "two", "three", "four")
    for(str in array) {
        println("value = " + str)
    }
}



This produces the following output:
value = one
value = two
value = three
value = four


Friday, 28 April 2017

Unity3d - Share Image on iOS and Android

The purpose of this code is to read in the title and body of the text getting shared. Then it takes a screenshot of the current screen in the game, saves it to a file, and then, depending on the platform, the screenshot is sent to the native Android or iOS share libraries with the title and body of the text that will be shared. This will present a native pop-up on the device showing what applications are available to share the media. This code is adapted from this github project: https://github.com/ChrisMaire/unity-native-sharing Start with an empty project and an empty scene

 The following directory structure should be created. A Scripts directory for the Share script was created for organizational purposes but this step is not necessary. It is very important to create the Plugins/iOS directories exactly as shown below.




Create the code to Share content

A Create a class called Share.cs under Scripts with the following contents.

using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using UnityEngine;

public class Share : MonoBehaviour {
 public string title;
 public string bodyText;

 public void SaveAndShareImage() {
  string destination = Path.Combine(Application.persistentDataPath, "test.png");
  SaveImage (destination);
  ShareImage (destination, title, 
   bodyText);
 }

 void ShareImage(string imageLocation, string titleText, string bodyText) {
  #if UNITY_IOS
  CallSocialShareAdvanced(bodyText, titleText, "", imageLocation);
  #endif

  #if UNITY_ANDROID
  AndroidJavaClass intentClass = new AndroidJavaClass("android.content.Intent");
  AndroidJavaObject intentObject = new AndroidJavaObject("android.content.Intent");
  intentObject.Call("setAction", intentClass.GetStatic("ACTION_SEND"));
  AndroidJavaClass uriClass = new AndroidJavaClass("android.net.Uri");
  AndroidJavaObject uriObject = uriClass.CallStatic("parse","file://" + imageLocation);
  intentObject.Call("putExtra", intentClass.GetStatic("EXTRA_STREAM"), uriObject);
  intentObject.Call("putExtra", intentClass.GetStatic("EXTRA_TEXT"), bodyText);
  intentObject.Call("putExtra", intentClass.GetStatic("EXTRA_SUBJECT"), titleText);
  intentObject.Call("setType", "image/jpeg");
  AndroidJavaClass unity = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
  AndroidJavaObject currentActivity = unity.GetStatic("currentActivity");

  currentActivity.Call("startActivity", intentObject);
  #endif
 }

 void SaveImage(string destination) {
  Texture2D screenTexture = new Texture2D(Screen.width, Screen.height, TextureFormat.RGB24, true);
  screenTexture.ReadPixels(new Rect(0, 0, Screen.width, Screen.height),0,0);
  screenTexture.Apply();
  byte[] dataToSave = screenTexture.EncodeToPNG();
  File.WriteAllBytes(destination, dataToSave);
 }

 #if UNITY_IOS
 public struct ConfigStruct
 {
  public string title;
  public string message;
 }

 [DllImport ("__Internal")] private static extern void showAlertMessage(ref ConfigStruct conf);

 public struct SocialSharingStruct
 {
  public string text;
  public string url;
  public string image;
  public string subject;
 }

 [DllImport ("__Internal")] private static extern void showSocialSharing(ref SocialSharingStruct conf);

 public static void CallSocialShare(string title, string message)
 {
  ConfigStruct conf = new ConfigStruct();
  conf.title  = title;
  conf.message = message;
  showAlertMessage(ref conf);
 }


 public static void CallSocialShareAdvanced(string defaultTxt, string subject, string url, string img)
 {
  SocialSharingStruct conf = new SocialSharingStruct();
  conf.text = defaultTxt;
  conf.url = url;
  conf.image = img;
  conf.subject = subject;

  showSocialSharing(ref conf);
 }
 #endif
}

Create an iOS plugin

At this stage, there is enough code to get Android sharing working. Unity requires a native iOS plugin to do native sharing. An iOS plugin with two more files need to be created. The files that need to be created are:
Plugins/iOS/iOSNativeShare.h
Plugins/iOS/iOSNativeShare.m
Then make sure that these files have the following Objective C code. The following code was taken from https://github.com/ChrisMaire/unity-native-sharing Plugins/iOS/iOSNativeShare.h
#import "UnityAppController.h"

@interface iOSNativeShare : UIViewController
{
    UINavigationController *navController;
}


struct ConfigStruct {
    char* title;
    char* message;
};

struct SocialSharingStruct {
    char* text;
    char* url;
    char* image;
    char* subject;
};


#ifdef __cplusplus
extern "C" {
#endif
    
    void showAlertMessage(struct ConfigStruct *confStruct);
    void showSocialSharing(struct SocialSharingStruct *confStruct);
    
#ifdef __cplusplus
}
#endif


@end



Next, add the following code to Plugins/iOS/iOSNativeShare.m






#import "iOSNativeShare.h"

@implementation iOSNativeShare{
}

#ifdef UNITY_4_0 || UNITY_5_0

#import "iPhone_View.h"

#else

extern UIViewController* UnityGetGLViewController();

#endif

+(id) withTitle:(char*)title withMessage:(char*)message{
 
 return [[iOSNativeShare alloc] initWithTitle:title withMessage:message];
}

-(id) initWithTitle:(char*)title withMessage:(char*)message{
 
 self = [super init];
 
 if( !self ) return self;
 
 ShowAlertMessage([[NSString alloc] initWithUTF8String:title], [[NSString alloc] initWithUTF8String:message]);
 
 return self;
 
}

void ShowAlertMessage (NSString *title, NSString *message){
 
 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title
                       
                       message:message
                       
                       delegate:nil
                       
                       cancelButtonTitle:@"OK"
                       
                       otherButtonTitles: nil];
 
 [alert show];
 
}

+(id) withText:(char*)text withURL:(char*)url withImage:(char*)image withSubject:(char*)subject{
 
 return [[iOSNativeShare alloc] initWithText:text withURL:url withImage:image withSubject:subject];
}

-(id) initWithText:(char*)text withURL:(char*)url withImage:(char*)image withSubject:(char*)subject{
 
 self = [super init];
 
 if( !self ) return self;
 
 
 
    NSString *mText = text ? [[NSString alloc] initWithUTF8String:text] : nil;
 
    NSString *mUrl = url ? [[NSString alloc] initWithUTF8String:url] : nil;
 
    NSString *mImage = image ? [[NSString alloc] initWithUTF8String:image] : nil;
 
    NSString *mSubject = subject ? [[NSString alloc] initWithUTF8String:subject] : nil;
 
 
 NSMutableArray *items = [NSMutableArray new];
 
 if(mText != NULL && mText.length > 0){
  
  [items addObject:mText];
  
 }
 
 if(mUrl != NULL && mUrl.length > 0){
  
  NSURL *formattedURL = [NSURL URLWithString:mUrl];
  
  [items addObject:formattedURL];
  
 }
 if(mImage != NULL && mImage.length > 0){
  
  if([mImage hasPrefix:@"http"])
  {
   NSURL *urlImage = [NSURL URLWithString:mImage];
   
            NSError *error = nil;
            NSData *dataImage = [NSData dataWithContentsOfURL:urlImage options:0 error:&error];
            
            if (!error) {
                UIImage *imageFromUrl = [UIImage imageWithData:dataImage];
                [items addObject:imageFromUrl];
            } else {
                ShowAlertMessage(@"Error", @"Cannot load image");
            }
        }
        else if ( [self isStringValideBase64:mImage]){
            NSData* imageBase64Data = [[NSData alloc]initWithBase64Encoding:mImage];
            UIImage* image = [UIImage imageWithData:imageBase64Data];
            if (image!= nil){
                [items addObject:image];
            }
            else{
                ShowAlertMessage(@"Error", @"Cannot load image");
            }
        }
        else{
   NSFileManager *fileMgr = [NSFileManager defaultManager];
   if([fileMgr fileExistsAtPath:mImage]){
    
    NSData *dataImage = [NSData dataWithContentsOfFile:mImage];
    
    UIImage *imageFromUrl = [UIImage imageWithData:dataImage];
    
    [items addObject:imageFromUrl];
   }else{
    ShowAlertMessage(@"Error", @"Cannot find image");
   }
  }
 }
 
 UIActivityViewController *activity = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:Nil];
 
    if(mSubject != NULL) {
        [activity setValue:mSubject forKey:@"subject"];
    } else {
        [activity setValue:@"" forKey:@"subject"];
    }
 
 UIViewController *rootViewController = UnityGetGLViewController();
    //if iPhone
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
          [rootViewController presentViewController:activity animated:YES completion:Nil];
    }
    //if iPad
    else {
        // Change Rect to position Popover
        UIPopoverController *popup = [[UIPopoverController alloc] initWithContentViewController:activity];
        [popup presentPopoverFromRect:CGRectMake(rootViewController.view.frame.size.width/2, rootViewController.view.frame.size.height/4, 0, 0)inView:rootViewController.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
    }
    return self;
}

-(BOOL) isStringValideBase64:(NSString*)string{
    
    NSString *regExPattern = @"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$";
    
    NSRegularExpression *regEx = [[NSRegularExpression alloc] initWithPattern:regExPattern options:NSRegularExpressionCaseInsensitive error:nil];
    NSUInteger regExMatches = [regEx numberOfMatchesInString:string options:0 range:NSMakeRange(0, [string length])];
    return regExMatches != 0;
}

# pragma mark - C API
iOSNativeShare* instance;

void showAlertMessage(struct ConfigStruct *confStruct) {
 instance = [iOSNativeShare withTitle:confStruct->title withMessage:confStruct->message];
}

void showSocialSharing(struct SocialSharingStruct *confStruct) {
 instance = [iOSNativeShare withText:confStruct->text withURL:confStruct->url withImage:confStruct->image withSubject:confStruct->subject];
}

@end

Hook up the code to a button press

Now, to hook up this code to your application, you can trigger it from a button press.

Right click on the Hierarchy, select UI -> Button



That will create a Canvas and a Button in the Hierarchy, and some embedded text in the button. What we are interested in here is adding the Share script to the button. 
Select the button in the hierarchy and in the inspector, click "Add Component" and type Share and click on the script.


Now there are two text fields, one for the title of the share content and one for the share body text.


Type whatever you want to be in the body and in the title of the shared content in here.

Now click on "On Click ()" "+" button on the Button component in the inspector.



Click on the "None (Ob"... text field and select Button. Then in the dropdown with "No Function", select Share -> SendAndShareImage()


This makes the Share code to get triggered when the button is clicked. 

Next deploy the game to an iOS or Android device, and you will be able to share an image with a title and text


Saturday, 25 June 2016

Set up a Git Repository on Raspberry Pi with SSH

I have a Raspberry Pi on my local network and I wanted to set it up as a git repository on my local network.

First thing I did was SSH onto my Raspberry Pi and create a directory for the repo.
mkdir <project name>

Next I needed to set up the Git repo. 
cd <project name>
git init --bare

Now the repo was set up and I could clone it to any device on my network.
I cloned the repo onto my mac
git clone ssh://<username>@<raspberry pi ip or hostname>:<path to repo>/<repo name>

The repo was now cloned on my mac, I could make local commits. But to push to the remote repo over SSH, I needed to specify what branch was needed. When I performed git init --bare, no branches were actually created, so I needed to create and push to a new remote branch.
git push origin master



Thursday, 29 October 2015

Mac won't power up

Problem

Issue with powering on MacBook Pro Retina.
Tried a hard reset (hold power button down for 10 seconds).
This did not solve the issue.
When the power button was pressed, the Apple logo light on the laptop was on.
Toggling Caps-Lock did not power on or off the light in the Caps-Lock key.
There was some heat coming from the laptop when touching the bottom of if.

Solution

The solution was to reset the NVRAM.
To do this, do a hard poweroff (hold down the power button for about 10 seconds). Verify that the Apple logo on the laptop lid is not on.
Now press the power button.
Immediately after pressing the power button hold down the following keys. Command, Option, P and R. You will then hear the start up sound play.
Keep holding the keys down until the startup sound plays again, or till the login screen is displayed.


More details about this can be found on the Apple website https://support.apple.com/en-us/HT204063

Sunday, 19 July 2015

Unity 3D - Red Lines in Scene View when importing UI Prefab

I had an issue importing a UI prefab using Unity 5. When I imported it, there were a lot a red lines over the scene. The prefab was not showing up in the scene view or game view. It appears as if the UI prefab is corrupted or is invalid.


The problem here is that the UI prefab was not imported into the UI canvas. 
This is fixed by:
  1. Removing the prefab from the scene
  2. Add a UI Canvas to the scene
  3. Add the prefab to the Canvas

Saturday, 20 June 2015

Auto Position Game Objects in Random Positions in Unity

I was working on a 2D game in Unity and I wanted a lot of game objects placed randomly in the level but I didn't want to spend a lot of time laying out each individual game object. I wanted to generate a lot of clouds in a level but without the hassle of manually laying them all out.

I achieved this by creating and attaching a script that would automatically layout the position of all the game objects in the level.


This is what an example of what the level looks like.

I created a C# script called GameObjectGenerator.cs.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

/***
 * Programatically generate a list of GameObjects at random positions 
 * between xOffset to xLength, yOffset to yLength and zOffset to zLength.
 */
public class GameObjectGenerator : MonoBehaviour {
 
 /***
  * The maximum number of objects to get generated
  */
 public int maxObjects = 10;

 /***
  * The type of game object to generate
  */
 public GameObject generatedObjectType;

 /***
  * The x-axis offset to position the game objects
  */
 public float xOffset = 0f;
 /***
  * The y-axis offset to position the game objects
  */
 public float yOffset = 0f;
 /***
  * The z-axis offset to position the game objects
  */
 public float zOffset = 0f;

 /***
  * The length of the level on the x-axis
  */
 public float xLength = 10f;
 /***
  * The length of the level on the y-axis
  */
 public float yLength = 10f;
 /***
  * The length of the level on the z-axis
  */
 public float zLength = 10f;

 /***
  * Internal list of all the generated game objects. 
  */
 private List gameObjects;

 void Start () {
  gameObjects = new List();
  generateGameObjects ();
 }
 
 public void generateGameObjects() {
  for (int i = 0; i < maxObjects; i++) {
   //get a random value between the offset and the length
   float xPos = Mathf.Clamp(xOffset + Random.value * xLength, xOffset, xLength);
   float yPos = Mathf.Clamp(yOffset + Random.value * yLength, yOffset, yLength);
   float zPos = Mathf.Clamp(zOffset + Random.value * zLength, zOffset, zLength);

   Vector3 position = new Vector3 (xPos, yPos, zPos);

   GameObject gameObject = (GameObject)Instantiate (generatedObjectType, position, Quaternion.identity);
   gameObjects.Add(gameObject);
  }
 }
}



First thing to do to get this working, is to create an empty game object and added it to the scene. I called it CloudGenerator. Attach the GameObjectGenerator script to the CloudGenerator. Once the script is attached, you can see all the parameters that can be changed for the script.

 
Description
Max Objects is the number of Game Objects that are going to be instantiated in the level.
Generated Object Type is the game object that is going to be generated throughout the level. In my case, it was a cloud sprite Game Object.
X Offset is the position on the X-Axis where the game objects will begin to get placed.
Y Offset is the position on the Y-Axis where the game objects will begin to get placed.
Z Offset is the position on the Z-Axis where the game objects will begin to get placed.
X Length is the length of the level in the X-Axis.
Y Length is the length of the level in the Y-Axis.
Z Length is the length of the level in the Z-Axis.

So from the example above, there will be 100 Cloud objects created in random positions from (-42, 4, -5) to (300, 7, 50).

This also brings parallax to the game since objects are created at random values on the Z-Axis. If you don't want parallax in your game, just set Z Offset and Z Length to 0.

Monday, 4 May 2015

Simple Photo Cropper in Java

I recently scanned in an old box of photos for my family. The scanned image had a few photos surrounded by a lot of whitespace.

I didn't want to have to open each photo manually, crop it, choose a file name, repeat. I wanted to have some of this process automated.

I wrote this simple program to make this process easier.

First you choose a directory to work from.
Once this is done, the first image from the directory is loaded.
You can then click and drag a box around an image the image you want to crop.
Once you are happy with the image selection, click the Crop button. This will save the selection to a file.
The file name is the time since epoch in milliseconds.
The file format is png.
The files are saved in a "cropped" directory. This is in the same directory as the original images.

You can then repeat this multiple times per image.
Once you want to work on the next image, you can click the next button for the next image or the prev button for the previous image.
There is a slider allows that allows to zoom in and zoom out of the image.




package photocropper;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;

/***
 * This is the main class of the application. It constructs the UI and 
 * registers event listeners,
 * @author andrew
 *
 */
public class PhotoCropper {

	private ArrayList photos;
	private int currentImageIndex = -1;
	private String directory;

	public PhotoCropper() throws Exception {
		frame = new JFrame();
		panel = new ImagePanel();

		while((directory = getPhotoDirectory()) == null) {
			System.err.println("This application needs to have a photo directory. Exiting...");
			System.exit(1);
		}	

		String[] files = Utils.getFiles(directory);

		ArrayList fileList = Utils.toArrayList(files);
		Utils.filterNonImages(fileList);
		photos = fileList;

		panel.setOutputDirectory(directory + File.separator + "cropped");

		CropMouseListener cropListener = new CropMouseListener(panel);
		panel.addMouseListener(cropListener);
		panel.addMouseMotionListener(cropListener);

		if(photos.size() > 0) {
			BufferedImage img = Utils.loadImage(directory, photos.get(0));
			frame.setTitle(photos.get(0)+ "\t" + 0 + "/" + photos.size());
			currentImageIndex = 0;
			panel.changeImage(img);
		}

		JPanel controlPanel = new JPanel();

		btnNext = new JButton("Next");
		btnPrev = new JButton("Prev");
		btnCrop = new JButton("Crop");

		sliderZoom = new JSlider(1, 100);
		sliderZoom.setValue(100);
		setUpListeners();
		controlPanel.add(btnPrev);
		controlPanel.add(sliderZoom);
		controlPanel.add(btnNext);
		controlPanel.add(btnCrop);

		//set resizeable to false so the resize will not interfere 
		//when interacting with images close to the border
		frame.setResizable(false);

		frame.add(controlPanel, BorderLayout.SOUTH);

		frame.add(panel, BorderLayout.CENTER);

		frame.setSize(new Dimension(1400,800));
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setVisible(true);
	}

	/***
	 * 
	 * @return
	 */
	public String getPhotoDirectory() {
		JFileChooser fileChooser = new JFileChooser();
		String dir = null;
		fileChooser.setDialogTitle("Select Photo Directory");

		fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);

		int returnVal = fileChooser.showOpenDialog(frame);
		if (returnVal == JFileChooser.APPROVE_OPTION) {
			dir = fileChooser.getSelectedFile().getAbsolutePath();
		}

		return dir;
	}

	public void setUpListeners() {
		btnNext.addActionListener((ActionEvent evt) -> {
			if((currentImageIndex + 1) >= photos.size()) {
				currentImageIndex = 0;
			}
			else {
				currentImageIndex++;
			}

			try {
				BufferedImage prevImage = Utils.loadImage(directory, photos.get(currentImageIndex));
				frame.setTitle(photos.get(currentImageIndex) + "\t" + currentImageIndex + "/" + photos.size());
				panel.changeImage(prevImage);
				frame.repaint();
			}
			catch(IOException e) {
				System.err.println("ERROR: could not load " + photos.get(currentImageIndex));
				e.printStackTrace();
			}
		});

		btnPrev.addActionListener((ActionEvent evt) -> {
			if((currentImageIndex - 1) < 0) {
				currentImageIndex = photos.size() -1;
			}
			else {
				currentImageIndex--;
			}

			try {
				BufferedImage prevImage = Utils.loadImage(directory, photos.get(currentImageIndex));
				frame.setTitle(photos.get(currentImageIndex) + "\t" + currentImageIndex + "/" + photos.size());
				panel.changeImage(prevImage);
				frame.repaint();
			}
			catch(IOException e) {
				System.err.println("ERROR: could not load " + photos.get(currentImageIndex));
				e.printStackTrace();
			}
		});

		btnCrop.addActionListener((ActionEvent evt) -> {
			try{
				panel.crop();
			}
			catch(Exception e) {
				JOptionPane.showMessageDialog(frame,
					    "Could not crop image. Bad image selection",
					    "Crop Error",
					    JOptionPane.ERROR_MESSAGE);
			}
		});

		sliderZoom.addChangeListener((ChangeEvent e) -> {
			double value = sliderZoom.getValue();
			double zoom = value / 100.0;
			panel.setZoom(zoom);
		});
	}

	public static void main(String[] args) {
		try {
			new PhotoCropper(); 			
		}
		catch(Exception e) {
			e.printStackTrace();
		}
	}

	private JButton btnNext;
	private JButton btnPrev;
	private JButton btnCrop;
	private JSlider sliderZoom;
	private ImagePanel panel;
	private JFrame frame;
}

package photocropper;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JPanel;

/***
 * This component displays the image.
 * It also has functionality to crop the image.
 *  
 * @author andrew
 *
 */
public class ImagePanel extends JPanel {

	private double zoom;
	private double scale;

	/**
	 * 
	 */
	private static final long serialVersionUID = -2179167993341600319L;

	private BufferedImage displayImage;
	private CropBox cropBox;
	private String dir;

	public ImagePanel() {
		cropBox = new CropBox(0, 0, 0, 0);
		zoom = 1.0;
		scale = 1.0;
		dir = "";
	}

	/***
	 * Change the image that is displayed in the panel
	 * @param image
	 */
	public void changeImage(BufferedImage image) {
		displayImage = image;
	}

	/***
	 * Set the first coordinate of the crop box.
	 * This is the top corner of the crop box
	 * @param x
	 * @param y
	 */
	public void setFirstCoord(int x, int y) {
		cropBox.x = x;
		cropBox.y = y;
		this.repaint();
	}

	/***
	 * Set the second coordinate of the crop box.
	 * This is the bottom corner of the crop box.
	 * @param x
	 * @param y
	 */
	public void setSecondCoord(int x, int y) {
		cropBox.width = x - cropBox.x;
		cropBox.height = y - cropBox.y;
		this.repaint();
	}

	/***
	 * Set the size of the crop box
	 * @param width
	 * @param height
	 */
	public void setCropBoxSize(int width, int height) {
		cropBox.width = width;
		cropBox.height = height;
		this.repaint();
	}
	
	/***
	 * Set the zoom. This allows zooming in and out of the display image
	 * @param zoom
	 */
	public void setZoom(double zoom) {
		this.zoom = zoom;
		this.scale = 1 / zoom;
		this.repaint();
	}

	/***
	 * Draw the component. 
	 * Draw the image then superimpose the cropbox on top of it.
	 */
	@Override
	public void paintComponent(Graphics graphics) {
		Graphics2D g = (Graphics2D) graphics;

		if(displayImage != null) {
			AffineTransform at = new AffineTransform();
			at.setToScale(zoom, zoom);
			g.drawImage(displayImage, at, this);
		}
		else {
			g.drawString("Image Not Available", (int)(getWidth()/2.5), getHeight()/2);
		}

		if(cropBox != null) {
			Color oldColor = g.getColor();
			g.setColor(cropBox.color);
			g.drawRect(cropBox.x, cropBox.y, cropBox.width, cropBox.height);

			g.setColor(oldColor);
		}
	}

	/***
	 * Crop the image selection and save to file
	 * @throws Exception
	 */
	public void crop() throws Exception {
		try{
			if(displayImage == null) {
				System.err.println("ERROR: No image to crop");
				return;
			}

			File outFile = new File(dir +   File.separator + System.currentTimeMillis() + ".png");
			
			//get the crop box coords with respect to the zoom property
			int x = (int)(cropBox.x * scale);
			int y = (int)(cropBox.y * scale);
			int width = (int)(cropBox.width *scale);
			int height = (int)(cropBox.height * scale);

			//reset for bad input
			if(x < 0) x = 0;
			if(y < 0) y = 0;
			if(width < 0) width = 0;
			if(height < 0) height = 0;

			//correct the width if it is out of bounds
			if((x + width) > displayImage.getWidth()) {
				width = displayImage.getWidth() - x;				
			}

			//correct the height if it is out of bounds
			if((y + height) > displayImage.getHeight()) {
				height = displayImage.getHeight() - y;
			}

			//throw exception if selection is not in the image at all
			if(x > displayImage.getWidth()) {
				throw new Exception("Image selection is out of bounds");
			}
			if(y > displayImage.getHeight()) {
				throw new Exception("Image selection is out of bounds");
			}

			//crop the sub image to save
			BufferedImage img = displayImage.getSubimage(x, y, 
					width, height);

			//check for the destination directory, create it if it doesn't exist
			File outputDir = new File(dir);
			if(!outputDir.isDirectory()) {
				System.out.println("Output directory does not exist, creating: "
						+ outputDir.getAbsolutePath());
				outputDir.mkdir();
			}			

			ImageIO.write(img, "png", outFile);
		}
		catch (IOException e) {
			System.err.println("Could not save to file. Check directory permissions");
		}
	}

	/***
	 * Set the save directory
	 * @param dir
	 */
	public void setOutputDirectory(String dir) {
		this.dir = dir;
	}

	/***
	 * CropBox contains the properties of the cropping box
	 * @author andrew
	 */
	private class CropBox {
		private int x, y, width, height;
		private Color color;

		public CropBox(int x, int y, int width, int height) {
			this.x = x;
			this.y = y;
			this.width = width;
			this.height = height;
			this.color = Color.RED;
		}
	}
}


package photocropper;

import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

/***
 * This listens to mouse events and sets the coordinates 
 * of the bounding box of the crop. 
 * 
 * @author andrew
 */
public class CropMouseListener implements MouseListener, MouseMotionListener {
	private ImagePanel panel;
	
	public CropMouseListener(ImagePanel panel) {
		this.panel = panel;
	}
	
	@Override
	public void mouseReleased(MouseEvent e) {
		if(panel != null) {
			panel.setSecondCoord(e.getX(), e.getY());
		}
	}
	
	/***
	 * If right click, zero out any existing box
	 */
	@Override
	public void mousePressed(MouseEvent e) {
		if(panel != null) {
			panel.setCropBoxSize(0, 0);
			
			if(e.getButton() == MouseEvent.BUTTON3) {
				panel.setFirstCoord(0, 0);
				panel.setSecondCoord(e.getX(), e.getY());
				return;
			}
			
			panel.setFirstCoord(e.getX(), e.getY());
		}
	}

	@Override
	public void mouseDragged(MouseEvent e) {
		//TODO - this only works for positive width and height
		//rework this so that it accommodates for both
		//negative width and height
		panel.setSecondCoord(e.getX(), e.getY());	
	}
	
	@Override
	public void mouseExited(MouseEvent e) {
		//Not interested in this event
	}
	
	@Override
	public void mouseEntered(MouseEvent e) {
		//Not interested in this event
	}
	
	@Override
	public void mouseClicked(MouseEvent e) {
		//Not interested in this event
	}
	
	@Override
	public void mouseMoved(MouseEvent e) {
		//Not interested in this event
	}
}


package photocropper;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

import javax.imageio.ImageIO;

/***
 * Some utilities used in the photo cropper
 * @author andrew
 *
 */
public class Utils {
	
	/***
	 * Load an image from file
	 * @param directory the directory path to the image
	 * @param filename the filename of the image
	 * @return the image in memory
	 * @throws IOException Thrown if cannot open the image
	 */
	public static BufferedImage loadImage(String directory, String filename)
			throws IOException {
		BufferedImage image = null;
		String dir = "";
		
		if(!directory.endsWith("/")) {
			dir = directory + "/";
		}
		else {
			dir = directory;
		}
		
		File file = new File(dir + filename);
		try{
			image = ImageIO.read(file);
		}
		catch(IOException e) {
			System.err.println("cannot open file " + file.getAbsolutePath() + e.getLocalizedMessage());
			throw e;
		}
		return image;
	}
	
	/***
	 * Get the names of all the files in a directory
	 * @param dirName The path of the directory
	 * @return a list of the files in a directory
	 */
	public static String[] getFiles(String dirName) {
		File dir = new File(dirName);
		
		if(dir.isDirectory()) {
			String[] files = dir.list();
			
			return files;
		}
		return null;
	}
	
	/***
	 * Helper method to convert an array to an arraylist
	 * @param array The array to convert
	 * @return returns the contents of the array in an arraylist format
	 */
	public static ArrayList toArrayList(String[] array) {
		ArrayList list = new ArrayList<>();
		
		for(String item: array) {
			list.add(item);
		}
		return list;
	}
	
	/***
	 * Get the file extension from a filename. 
	 * This works by getting the last occurrence of the dot
	 * and extracting the characters after it.
	 * If test.txt is passed in, the string txt is returned.
	 * @param file The name of the file
	 * @return the file extension 
	 */
	public static String getExtension(String file) {
		String extension = null;
		
		if(file == null) {
			return null;
		}
		
		int indexOfDot = file.lastIndexOf(".");
		
		if(indexOfDot != -1) {
			extension = file.substring(indexOfDot + 1);
		}
		
		return extension;
	}
	
	/***
	 * Loop through a file list and remove any non-images.
	 * @param fileList
	 */
	public static void filterNonImages(ArrayList fileList) {
		ArrayList fileToBeRemoved = new ArrayList<>();
		
		//store list of which files need to be filtered out
		for(String file: fileList) {
			if(!isImage(file)) {
				fileToBeRemoved.add(file);
			}
		}
		
		//filter out files from the original list
		for(String file: fileToBeRemoved) {
			fileList.remove(file);			
		}
	}
	
	/***
	 * Check if the file is an image.
	 * This is done based on the file extension
	 * @param filename
	 * @return
	 */
	public static boolean isImage(String filename) {
		String[] imageExtensions = {"png", "jpg"};
		
		String extension = getExtension(filename);
		
		if(extension == null) {
			return false;
		}
		
		for(String ext: imageExtensions) {
			if(ext.equals(extension.toLowerCase())) {
				return true;
			}
		}
		
		return false;
	}
}

Tuesday, 13 January 2015

Deploy Unity 3D Project to Samsung Galaxy Tab

Install the drivers

Install the Samsung Kies software
It can be downloaded from http://www.samsung.com/ie/support/usefulsoftware/KIES/

Enable Developer Options on the Tablet

On the tablet
Go to Settings -> General -> Left hand panel, scroll down to About device.
In the About device main panel, scroll down to Build number and tap it 7 times.
Quit the settings application.
Go to Settings -> General -> Left hand panel, scroll down to Developer options.
Tab the Enable USB debugging checkbox

Configure the Build Settings

In Unity go to File -> Build Settings...
In the Platform panel click Android and then click the Switch Platform button
Click on Add Current to add your scene to the android package. If you have multiple scenes in the game, make sure to add them all and make sure that they are in the correct order

Build the Game and Deploy to the Tablet

Then in Unity do File -> Build and Run

The first time you perform a build and run, you should get a notification on the tablet to allow the application to get pushed to the tablet. Allow this. Pushing the game to the device might fail, if it does click Build and Run again in Unity. It should start the game on the tablet.

Wednesday, 10 December 2014

Disable Screen Auto Rotation on Unity3D

Disable screen auto-rotation in a game in Unity. 

Option 1


Create a script that disables auto rotate to portrait and sets the orientation to landscape.

using UnityEngine;

public class CameraScript : MonoBehaviour {

    void Start() {
        DisableAutoRotation ();
    }

    public void DisableAutoRotation() {
        Screen.autorotateToPortrait = false;
        Screen.autorotateToPortraitUpsideDown = false;
        Screen.orientation = ScreenOrientation.Landscape;
    }
}



Create a game object to attach the script to. This can be any game object. You could also attach the script to the main camera in the scene either. When the game is run on hand held devices, the autorotation will be disabled.

Option 2


Another solution to this problem is to change this setting when building the project. 
Go to 
File -> Build Settings...




Click on Player Settings...


Click on Resolution and Presentations panel in the inspector. This will then expand and present more options.




In the Resolution and Presentation panel there are options for configuring the orientation.
First select the default Orientation. This is the orientation that the game will start in. 
You can configure what orientations are allowed when the game is being run. To do this, check or uncheck the required boxes for "Allowed Orientations for Auto Rotation".



Monday, 13 October 2014

Setting up Minidlna on Linux Mint

Minidlna is useful for broadcasting media around your home network. Multimedia can be streamed from the minidlna server to different upnp enabled devices such as computers/smartphones/game consoles.

Install the Pre-requisites
sudo apt-get install software-properties-common python-software-properties


Add the repository to get minidlna
sudo add-apt-repository ppa:djart/mindlna


update the repo
sudo apt-get update


Install minidlna
sudo apt-get install minidlna


Change the configuration file
sudo vim /etc/minidlna.conf


Change the user in the file to whatever user you want to use
Comment out the media_dir
Add an Audio directory, Video directory, Pictures directory. Also add the location that the Log files will be kept and where the database file will be kept
media_dir=A,/home/myuser/Music
media_dir=P,/home/myuser/Pictures
media_dir=V,/home/myuser/Videos
db_dir=/home/myuser/Cache
log_dir=/home/myuser/Log

Change the name of the DLNA server
friendly_name=myServerName


Restart the minidlna server
sudo service minidlna restart


If you see this error, Cannot connect to server, make sure to reload the server with the new content,
minidlnad -R

Thursday, 12 June 2014

Allow Remote Connections to MySQL Database on Raspberry Pi

This article shows how to set up a MySQL database to allow remote client connections. This will allow other programs on remote computers to access the database.
The first step is to allow machines to connect. This is done by removing the bind-address line from the configuration file /etc/mysql/my.cnf

Once this is done restart the database to pick up the changes
/etc/init.d/mysql restart

Once the database is restarted, we need to create database user for the remote clients. We create a user called remoteuser.
CREATE USER 'remoteuser'@'%' IDENTIFIED BY 'remoteuser';

Next, we grant all permissions to remoteuser for our database, mydatabase
GRANT ALL ON mydatabase.* TO 'remoteuser'@'%';

Finally, we set the password for the user to 'remotepassword'.
SET PASSWORD FOR 'remoteuser'@'%'=password('remotepassword');

Now external clients can connect remotely to the database and should be able to execute commands against it.

Thursday, 20 March 2014

Create a Simple Kernel Module on Ubuntu

Create a file, hello.c, that prints "Hello, world" when the module is loaded and prints "Goodbye, cruel world" when the module is unloaded
#include 
#include 
MODULE_LICENSE("Dual BSD/GPL");

static int hello_init(void) {
        printk(KERN_ALERT "Hello, world\n");
        return 0;
}

static void hello_exit(void) {
        printk(KERN_ALERT "Goodbye, cruel world\n");
}

module_init(hello_init);
module_exit(hello_exit);

Create a Makefile
ifneq ($(KERNELRELEASE),)
        obj-m := hello.o

else
        KERNELDIR ?= /lib/modules/$(shell uname -r)/build
        PWD := $(shell pwd)

default:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif

Running make will create the kernel module named hello.ko
make

load the module by using the following command
sudo insmod hello.ko
View the messages by entering the dmesg command.
dmesg

unload the module by using the following command
sudo rmmod hello

Sunday, 16 March 2014

Playing Internet Radio Stations on Raspberry Pi

Install mpd and mpc. MPD is the music player and MPC is a client to control the music player
sudo apt-get install mpc mpd

Once these are installed, you just need to have a stream to play from. I found this site useful for getting streams: http://www.listenlive.eu/index.html
Add the stream to the playlist
mpc add http://stream.hoerradar.de:80/deltaradio-alternative128
Play the station using the following command.
mpc play
Use the following command to stop the player.
mpc stop
Once the station is added, you will only need to use mpc play or mpc stop to control the player.

Add Syntax Highlighting in Vim

Syntax highlighting is extremely helpful when editing code. Vim has syntax highlighting built in to it but it is not enabled by default. To enable syntax highlighting, edit the Vim configuration file, /etc/vim/vimrc, and add "syntax on" to the file and then restart Vim.

Show line numbers in Vim

Adding line numbers in Vim is very simple. To add line numbers in Vim, edit the vim configuration file, /etc/vim/vimrc and add "set number" to the file and then restart Vim.

Connect to Wifi network with Raspberry Pi

Edit the /etc/network/interfaces file. You will need to use sudo when editing the file.

sudo vim /etc/network/interfaces


The contents of the file should have the following, where you have to substitute your_network and network_password are the ssid and the password of your network:
auto lo

iface lo inet loopback
iface eth0 inet dhcp

allow-hotplug wlan0
auto wlan0

iface wlan0 inet dhcp
        wpa-ssid "your_network"
        wpa-psk "network_password"


Restart networking to pick up the changes
sudo /etc/init.d/networking restart