Parsing Photos

(Part 3 of the Panzer General Portable Project)

When I created Panzer General Portable (PGP) for the windows phone, I had to deal with creating a game board based on a a single image file that looks like this:

image

There are 12 rows and 20 columns of images – each image is 60 pixels wide and 50 pixels wide.  Each image represents a hex on the game board.  When I did the Windows Phone app, I loaded this image each time in needed a hex – not the most efficient use of resources to be sure.  In addition to these game board images, there are similar composite images of units, country flags, etc..  The game board is a series of overlaid images for a given hex. The application keeps track of the correct image to be used via the x/y coordinates of the hex.  For example, the top left fix has a x/y of 0,0.

image

 

In other parts of the app, an index is used so the 0,0 hex is also index 0 with the 1,19 hex having an index value of 20.

image

 

You will also note that each image is a rectangle, and the area outside of the hex is a pink color.  Apparently, back in the old days, that color meant “transparent” to windows.  However, it does not to the iOS and Android, so that color needs to be converted into transparent

I fired up a new FSharp project in Visual Studio for Mac and created a F# script

I created a function that takes in the path to this composite image on my file system, a target directory where I want the small image to be written to, the name of that file, and the x y coordinates to locate the smaller image in the composite image:

1 let createHexImage sourcePath targetDirectory (fileName:string) x y = 2 let width = 60 3 let height = 50 4 let index = x + (y * 20) 5 use sourceImage = Image.FromFile(sourcePath) 6 use bitmap = new Bitmap(width,height) 7 use graphics = Graphics.FromImage(bitmap) 8 let targetRectangle = new Rectangle(0,0,width,height) 9 let sourceRectangle = new Rectangle(width*x,height*y,width,height) 10 graphics.DrawImage(sourceImage,targetRectangle,sourceRectangle,GraphicsUnit.Pixel) 11 graphics.Flush() 12 let transparentBitmap = createTransparentBitmap bitmap 13 let fileName = fileName.Replace("_",String.Empty) 14 let fileName' = targetDirectory + "//" + fileName + index.ToString() + ".jpeg" 15 transparentBitmap.Save(fileName') 16

The code is pretty straight forward File I/O and image creation.  Notice on line 12 a function called “createTransparentBitmap” is called to turn the pink into transparent.  That function is actually step 0 of a function chain like this (BTW: read from the bottom up in F# world):

1 let updateColor (color:Color) = 2 match color.R, color.G, color.B with 3 | 255uy,225uy,225uy -> Color.FromArgb(0x00FFFFFF) 4 | _,_,_ -> color 5 6 let getCoordinates index (bitmap:Bitmap) = 7 let y = index / bitmap.Width 8 let x = index % bitmap.Width 9 x,y 10 11 let adjustColor index (bitmap:Bitmap) = 12 let coordinates = getCoordinates index bitmap 13 let x = fst coordinates 14 let y = snd coordinates 15 let color = bitmap.GetPixel(x,y) 16 let updatedColor = updateColor color 17 bitmap.SetPixel(x,y,updatedColor) 18 19 let createTransparentBitmap (bitmap:Bitmap) = 20 let totalPixels = bitmap.Height * bitmap.Width 21 [0 .. totalPixels - 1] 22 |> Seq.iter(fun i -> adjustColor i bitmap) 23 bitmap

  • updateColor looks at a given pixel and if it is “pink”, it returns a new pixel of transparent, else it returns the pixel that came in (note that System drawing uses Pixel and Color interchangeably, which can be confusing)
  • getCordinates gets the pixel from the x,y coordinate of the bitmap
  • adjustColor takes in a bitmap and an index – it uses the index to locate the targeted pixel, updates the color if needed, and then sets a new pixel into the bitmap with the updated pixel.
  • createTransparentBitmap takes in the bitmap, calculates the total pixels, creates an array of integers, and then calls the adjustColor function

So after running this script, you can see my file system has a list of all of the images that the game needs:

image

The gist is here

 

 

 

 

g

Advertisements

Setting Up The Environment For Mobile Development

(Part 2 of the Panzer General Portable Project)

I tried, I really did, to use my ultra-hyped personal computer running Win10 and Visual Studio 2017 to create a Xamarin phone app.  Things were actually OK until I tried to launch the emulator for iOS and Android.  The machine just fell on its face and after waiting several minutes for the simulator to launch… and then crash, I gave up.

Also, since you need a Mac to publish to the Apple store, it made sense to go out and get a MacBook.  I know that you can “rent a Mac” for Apple store deployment using services like Azure Dev Ops, but:

  • I would have to develop on a PC (see the paragraph above)
  • I couldn’t debug on a real device
  • Everyone, I mean everyone, I talked to about writing a phone app said to get a Mac to reduce friction
  • I could finally look like a hipster hacker at the local Starbucks

Getting started with a MacBook wasn’t particular hard – though I had to retrain some muscle memory for the keyboard shortcuts.  Visual Studio for Mac certainly has some quirks that are worth mentioning (some of which I documented here):

1) VS sits on top of XCode.  If XCode has an update, VS may not recognize it, so it will break.  A rule of thumb is to manually update XCode whenever there is a VS update, and manually update VS whenever there is an XCode Update

2) Managing files in the .fsproj file is not seamless.  F# requires the files to be in a certain order for dependency management which allows the awesomeness of inferred typing when compiling.  In Visual Studio for the PC, you can hold down the shift key and move your files up and down.  There is no such feature in VS for Mac so you need to open the .fsproj file and manually move things as demonstrated here

3) Some some random reason, my solution would stop building and I would get the following errors.

Screen Shot 2018-11-07 at 3.22.01 PM

I put it into User Voice here – until then, I just recreate a new project.  Obviously once I start coding for real, this is not a good solution,