Showing posts with label update. Show all posts
Showing posts with label update. Show all posts

Thursday, September 26, 2013

Frances's Tracing Game v1.04: On Coordinate Transforms

As one bug dies, another is born. So it goes, so it goes. :)

As  I prepared to publish version 1.03 of Frances's Tracing Game, I took a glance at the statistics and suggestions from the Google Play Store and noticed that many newer Androids couldn't pick up the latest build because I had the minimum SDK version set to 1. I forked the project (Play Store lets you publish multiple binaries under the same app depending on the version of the client's phone), then in the fork I bumped the minimum up to SDK 4 and targeted SDK 11.

That introduced a new oddity: Apparently, my game had been running in a compatibility mode this entire time (due to its claim that it targeted SDK 1); as a result, the screen had been scaled automatically to fit the ancient Android form factor seen in the days of the Nexus 1. When I bumped to SDK4, the compatibility mode went away and my shapes were suddenly far too small to be traced by even clever fingers (screen resolution has improved a lot since the Nexus One days). The Android screen format rules have an awful lot of complexity to them, but to make a long story short: it is best to assume that your client's device could fit just about any rectangular form factor, much like we do with desktop PC programming.

Fortunately, this is an old problem with some old solutions. Many graphics libraries (such as OpenGL) provide a layer of transformations to make it easy to convert from one coordinate space to another. Android's graphics SDK is no exception; the Canvas object supports scaling and translation methods to tweak the underlying matrix that maps pixels in the canvas to pixels on the screen. I'd already used translation previously to center the image; now I just need to use scaling to balloon the image to fit the width of the screen.

Here's the basics of the tweaks I made; if you want to see the full code, it's hosted on GitHub.

Step 1: Tweak the scale

Tweaking the scale is extremely straightforward in Android; there's a pair of scale() methods that wrap the matrix multiplication logic for convenience (note: if you haven't done graphics programming, matrix algebra is basically the bread and butter of shifting coordinate spaces; Wikipedia can get you started on the concepts). I'm using this method, which includes the concept of a pivot point (the point around which the scaling should occur) to scale from the center of my image.

The only interesting part is determining how much to scale. I want to take up about 80% of the space, so I'm solving for S in the equations
final_image_width = 80% * screen_width
final_image_width = S * initial_image_width

This resolves to S = 80% * screen_width / initial_image_width, which I store and use as the scaling factor to rescale the canvas. Since the canvas itself is scaled, the image, line thickness I use for drawing, and pink tracing overlays are all correctly rendered without having to change the rest of the drawing logic.

Step 2: Unscale the touch

The only remaining issue is that the touch events we receive don't go through the rendering logic to be scaled. So for touch events we get from the screen, we need to reverse the scaling operation to get them back into the image coordinate space to determine if they were close to one of the tracing lines. I could have done this by grabbing the canvas's matrix with getMatrix() and inverting it (for a given transformation matrix, inverting the matrix gives you the reverse transformation). But I got a little bit fancy instead; the steps to reverse the scaling of the point (factoring in the pivot) are basically as follows:
  1. subtract the pivot coordinates from the point coordinates (this would translate a point on the pivot to (0,0), which is what you want because that point doesn't move under scaling).
  2. Multiply the point coordinates by 1/scale_factor.
  3. Add the pivot coordinates back to the point coordinates (reversing the translation).
It's mathematically simple enough that I think it's clearer to just represent it directly without the matrix (though just using the matrix inverse might very well have been less code!).

The Result


Image from the game: A smiling face, with the word "Smile" beneath it.
I am not unhappy. :)

I'm pretty happy with the result. Frances's game looks good on a 7-inch tablet and on a smartphone. I also added a text label for each picture, because she's getting older and is starting to read and write. I passed the labels through the canvas transformation, which leads to some odd scaling that's dependent upon the underlying size of the traced image; I might make some time to clean that up later. 

Feel free to download it to your own device if you'd like; it's free and will never include ads (because what possible use could ads be in a toddler's tracing game other than to junk-up the advertiser's signal with unintended clicks?).

I have no idea if this simple game will hold her interest much longer; it may soon be time to make her something new. And there's still the question of what to make for Cecilia, but there's a bit of time there; she's not very interested in tablets right now because she just realized that when she kicks her legs, her bounce-chair moves. I have to admit, that's pretty cool.

Monday, May 27, 2013

Frances's Tracing Game v1.03

I love three-day weekends. They give you time to think!

I've dusted off Frances's tracing game and finally fixed a niggling bug that had been bothering me. I'd taken a shortcut on centering the images on the screen by putting the image in a view that was embedded within a larger view. That solution had been playing passably, but it had annoying side-effects in Jellybean:
  1. Touch-drags that started outside the image didn't register when you dragged inward.
  2. As the drag operation happened, an annoying grey rectangle would flicker around the frame of the inner view. I never figued out the root cause of that.
I therefore ditched the two-view solution and did the math internally to set up the images in the center of the view (there's a convenient Canvas.translate method to do the offsetting for me in terms of drawing, and I do the translation of touch events from view coordinates to image coordinates myself).

There's a lesson in this. Sometimes, an operating system changes and it becomes clear that the OS's developers don't have your use case in mind. When that happens, there are a couple of ways you can respond. But when you're working on a hobby project and don't have the clout to steer the OS project itself, it's often easier to adapt to the changes than it is to push back. Don't be afraid to replace someone else's functionality with your own code. But when you can contribute, do; the svg-android renderer instance that I use seems to have issue with some flavors of transform logic, and if I don't find that this has been fixed in later versions, I plan to patch it and submit the changes back to the maintainers.

The new version of Frances's Tracing Game should be available in the app store some time today.

On a related note, there's one more bug to fix... I'm going to have to figure out what to do about the title. Frances herself is growing out of this game, but her younger sister was born this week. Welcome to the world, Cecilia!

Tuesday, August 7, 2012

Update to Frances's Tracing Game

Frances got a chance this weekend to play with her tracing game some more. She uncovered a bug I hadn't noticed in testing, and I noticed some trouble she was having. So I pushed an update.

Frame bug fix

A bit of inside-ball: under the hood, the traceable image is a custom View embedded in a LinearLayout, with the "gravity" on the LinearLayout set to center the embedded view horizontally and vertically. If Frances starts a trace by putting her finger down outside of the embedded view (i.e. inside the LinearLayout), the Android event system doesn't pass the event to the embedded view, so the trace doesn't get detected even as she moves her finger over the figure. This is perfectly understandable, correct by the UI standards, and 100% wrong for this application. I had to fish around a bit (there are a couple of options that look like they fix this issue and don't quite fit), but in the end the solution was to set the embedded view as the touch listener for the LinearLayout (i.e. embedded view implements OnTouchListener) and translate the coordinates from the LinearLayout's reference to the embedded view's reference using MotionEvent.offsetLocation.

Detect All Points

For simplicity, I'd been using only the first touch coordinate (the output of getX() and getY()) as the tracing location. However, watching Frances play the game, I noticed something about the way she holds the phone: rather than cradling the back, she usually grasps it from the side, which leaves her palm resting on the screen. Naturally, his plays havoc with the touch UI. Fortunately, the fix was simpler than the frame bug: I just read all the touch coordinates and try them all. If you put your palm on the image, that might count as a trace; I'm sort of fine with that, really.

Final Thought: On Users

Watching my niece play this game has been a great motivator for me to fix up the quirks. It's one thing to know you have thousands of anonymous users somewhere out in the world; it's quite another to have a two-year-old sitting in front of you straining her mind to try and understand a concept-breaking bug in the behavior of the program. The frame bug was driving her crazy, but she didn't want her uncle to do it for her; she was insistent on figuring it out herself. If she's going to be a trooper about it, the least I can do is clean up the last 2% of bugs in the toy.

By the time you read this, the latest version should be available on the Play Store. I didn't want to release just a bug update, so there's a couple of new figures in there too. Have fun!