Wednesday, May 22, 2013

GIF: "gif" vs "jif"

I never realized how many people say "gif" instead of "jif" (go head, click a letter), and while I personally don't care a lot, I have to say something about the argument for "gif."

Most of the arguments I've heard/read in favor of "gif" argue that it's Graphics Interchange Format and not Jraphics Interchange Format. "jif" people might respond that giraffe has a soft G, suggesting that GIF should follow suit. "gif" people might then respond that "giraffe" comes from French, and as such shouldn't be a model for pronouncing GIF.

But let's go back to that first argument of the "gif" people: that the G should be pronounced like it is in graphics (because that's what it stands for, after all). If that is the logic to which you submit, I have compiled a small list of other acronyms for which I hope you also apply the same logic:

  • laser: should be pronounced "la-seer"
  • scuba: should be pronounced "skuh-ba"
  • ASAP: should be pronounced "a-sap"
  • BASIC: should be pronounced "bah-sik"
  • Perl: should be pronounced "peh-rl"
  • SWAT: should be pronounced "swat" (not "swot")
  • FIFO: should be pronounced "fih-foh" (not "fahy-foh")

The list could go on, but hopefully that's enough to at least show that the argument that GIF should be "gif" because the G is for "graphics" is a weak argument at best.

FYI, this little rant has less to do with "gif" vs "jif" and more to do with me nitpicking people relying on a weak argument. That is all.

Saturday, May 4, 2013

Test Your YouTube Internet Speed

I'm pretty skeptical about Internet speeds. ISPs usually only guarantee you an "up to" speed, like "up to 50 Mbps." This kind of upper bound/maximum promise is, in my opinion, useless. They could promise you speeds "up to 99999 Mbps" and then only give you 1 Mbps (or less), and technically they have fulfilled their promise of giving you "up to 99999 Mbps" (think about it: the only thing they're promising is that they won't give you more than 99999 Mbps, so as long as they give you less than 99999 Mbps, they're keeping the promise). Comcast wouldn't give me any kind of guaranteed minimum or average speeds when I asked them, so I like to test my speeds occasionally to know what they're really giving me. And for good reason:


Sure, my average might be decent, but it's like a roller coaster with lots of disappointing low speeds. I'd prefer a consistent medium speed than a bipolar roller coaster.

Testing YouTube

I just learned about YouTube's speed testing. When you watch videos signed in to your YouTube/Google account, they keep track of how fast your connection is. So go to this page to test your YouTube speed. Near the bottom of that page, you can watch a test video to test your speed in real time. Play the test video. The instructions to 'look next to "Streaming HTTP"' are misleading, because "Streaming HTTP" appears nowhere on the page. Here's how to do it:

  1. Go to YouTube's speed testing page.
  2. Look at your charts to your hearts content.
  3. Click the "Show Test Video" link to show the test video.
  4. Right click on the video while it's playing and select "Show video info".
  5. Look at the statistics by "TagStreamPlayer, HTTP" to see what your kbps are (1000 kbps = 1 Mbps).
  6. If it says "0 kbps", click on the little cog wheel in the video and change the video quality (to anything). If it's saying 0 kbps but the video is playing, it needs help in refreshing its calculations, and by changing the video quality it refreshes things so that the real kbps should be showing.
  7. Change the video quality to whatever you want; I like to test on the highest quality.

The cool part? If you watch any video, you can right click on the video and "Show video info" to get these stats. You can also right click on a video and select "Take speed test" which will take you to this page.

Why not just a normal speed test?

There are several ways to test your Internet speed, but good speeds on one website don't mean good speeds on another website. Whether this is because of "clogged tubes," throttling, a busy server, slooow pings, or any number of other potential issues, is a mystery (unless you take the time to solve it). But if you want to know your speeds on YouTube... well, you might as well use YouTube itself to test.

Thursday, March 21, 2013

WebGL: Fixing "INVALID_OPERATION: drawArrays: attribs not setup correctly"

I kept getting the WebGL: INVALID_OPERATION: drawArrays: attribs not setup correctly error in my simple WebGL app, which wasn't very helpful to me. Ok, I get it's not setup correctly, but what could be wrong? It turns out the problem was being caused by me using two different shader programs, with each program having a different number of attributes. I want to walk through this clearly, because most of the things I found online when searching for solutions were... lacking, to put it kindly. Let's look at my shaders:

Shader Program #1

It's a pretty simple program. Given a set of 2D vertices, make them all white.

Vertex shader:
attribute vec2 vertex;

void main(void)
{
    gl_Position = vec4(vertex, 0.0, 1.0);
}
Fragment shader:
precision mediump float;

void main(void)
{
    gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}

Shader Program #2

Not too complicated. Given a set of 2D vertices and corresponding texture coordinates, render a textured object.

Vertex shader:
attribute vec2 vertex;
attribute vec2 textureCoord;

varying vec2 fragTextureCoord;

void main(void)
{
    gl_Position = vec4(vertex, 0.0, 1.0);
    fragTextureCoord = textureCoord;
}
Fragment shader:
precision mediump float;

uniform sampler2D sampler;

varying vec2 fragTextureCoord;

void main(void)
{
    gl_FragColor = texture2D(sampler, fragTextureCoord);
}

The problem

The problem is rooted in the shaders. The first vertex shader only has one attribute (vertex), while the second vertex shader has two attributes (vertex and textureCoord). When switching from one program to another, the enabled attributes weren't getting switched! In other words, when I loaded shader program #1 and called enableVertexAttribArray to enable the vertex attribute, and then later when I loaded shader program #2 and called enableVertexAttribArray to enable the vertex and textureCoord attributes, the enabling of the attributes isn't bound to a specific program. Enabling an attribute is a global operation, regardless of whether or not you create or use a different program. So when using shader program #1, the textureCoord attribute was still enabled! This resulted in an error (which seems sensible). There are two ways to work around this.

Solution #1: disableVertexAttribArray

One option is to just disable the extra attributes. When we use shader program #1, we can call disableVertexAttribArray to disable the extra attributes that shader program #2 uses. You have to watch out a little if you do this, because if you aren't explicitly assigning index numbers to your attributes, it's possible the vertex attribute in shader programs #1 and #2 will have different index values. One way to do this is to track which program you were using and which program you're going to use now, and then disable any extra attributes (or enable any missing attributes). Something like:

function switchPrograms(currentProgram, newProgram)
{
    // Gets the number of attributes in the current and new programs
    var currentAttributes = gl.getProgramParameter(currentProgram, gl.ACTIVE_ATTRIBUTES);
    var newAttributes = gl.getProgramParameter(newProgram, gl.ACTIVE_ATTRIBUTES);

    // Fortunately, in OpenGL, attribute index values are always assigned in the
    // range [0, ..., NUMBER_OF_VERTEX_ATTRIBUTES - 1], so we can use that to
    // enable or disable attributes
    if (newAttributes > currentAttributes) // We need to enable the missing attributes
    {
        for (var i = currentAttributes; i < newAttributes; i++)
        {
            gl.enableVertexAttribArray(i);
        }
    }
    else if (newAttributes < currentAttributes) // We need to disable the extra attributes
    {
        for (var i = newAttributes; i < currentAttributes; i++)
        {
            gl.disableVertexAttribArray(i);
        }
    }

    // With all the attributes now enabled/disabled as they need to be, let's switch!
    gl.useProgram(newProgram);
}

Note that the above solution assumes all your attributes are array attributes. If you have an attribute that isn't an array (i.e. it shouldn't be enabled with enableVertexAttribArray), you'll have to come up with a more robust solution. Hopefully the above at least gives you some ideas and can help point you in the right direction in your quest to come up with a valid solution.

Solution #2: vertexAttribPointer

Enable all the attributes! Alright, so the whole reason we were getting this INVALID_OPERATION error comes from here (I suggest you read it and try to understand it!). The key here is in the last statement: "If a vertex attribute is enabled as an array, a buffer is bound to that attribute, but the attribute is not consumed by the current program, then regardless of the size of the bound buffer, it will not cause any error to be generated during a call to drawArrays or drawElements."

This means that if your program doesn't use the attribute, it's okay to fill it with a tiny junk array (note: not null, as that has other implications), as it won't actually be used. This means you can bind a tiny junk buffer with bindBuffer and then set the superfluous attribute to that tiny junk buffer with vertexAttribPointer, and then continue on your merry way. All WebGL wants is for your enabled array attributes to be associated with some valid (non-null) array (which is why you get the INVALID_OPERATION error if they're not). If you enable all the attributes, but your program doesn't actually use an enabled attribute, WebGL doesn't care how big the array is that's associated with it (so you won't get any out-of-bounds errors). It's just happy it has an array associated with the enabled attribute.

Saturday, March 2, 2013

ASCII Snake

I've always thought the classical game of Snake is a fun game to program. One day I decided I wanted to speed-code a game of Snake, rendered entirely with (extended) ASCII art. One particular friend of mine made feature requests and gave me feedback as I quickly developed it.

In the end, the game was quite a bit of fun. It ran in a Windows console, had color, cheats, Easter eggs, and even a multiplayer mode (that operated kind of like a Tron Lightcycle battle). My friend and I made several good memories with this little game, competing for the high score and battling it out in multiplayer. If everything was quiet and a sudden "Dang it!" came echoing from another room, I knew a game of Snake was going down.

Because I was speed coding this, I'm not going to release the code (it's atrocious!). I will, however, for kicks and giggles, provide binaries which you may play with. You can download it here. See if you can figure out all the cheats and Easter eggs!

Haskell Mandelbrot

I wrote a program (years ago) in Haskell that generated the image above of the Mandelbrot set. I really like Haskell, but I haven't spent enough time with it and never really wrapped my head around monads. I'd like to dive back into it when I get the time.

Anyway, the program I used to generate the fractal is below. I'm not a Haskell guru, so if the code is an abomination and you know how to improve it, please share in the comments! It would be cool to improve my Haskell skills. I wrote this years ago:

module Main where

import Data.Complex
import Data.Char
import qualified Data.ByteString.Char8 as C -- For file output

-- Constants
maxIter :: Int -- Max iterations
maxIter = 750

width  :: Int -- Image width
height :: Int -- Image height

width  = 400
height = 400

-- Note: aspect ratio of (minX, minY), (maxX, maxY) must
-- match aspect ratio of (width, height)
minX :: Double -- Min x-coordinate of graph
maxX :: Double -- Max x-coordinate of graph
minY :: Double -- Min y-coordinate of graph
maxY :: Double -- Max y-coordinate of graph

-- For the zoomed in part of the Mandelbrot:
--minX = -0.826341244461360116
--maxX = -0.8026423086165848822

--minY = -0.2167936114403439588
--maxY = -0.193094675595568725

--For a full view of the mandelbrot
minX = -2.5
maxX = 1.5

minY = -2
maxY = 2


-- The actual fractal part
-- It basically works on a matrix, which we will call M, that represents a grid of
-- points on the graph. Essentially, M[i, j] is (xList[j], yList[i])
xList :: [Double]
yList :: [Double]

xList = [minX, (minX + ((maxX - minX) / (fromIntegral width  - 1)))..maxX]
yList = reverse [minY,(minY + ((maxY - minY) / (fromIntegral height - 1)))..maxY]

row :: Double -> C.ByteString -- A row of image bytes for a given y-coordinate
row y = C.pack [frac (x :+ y) (0 :+ 0) 0 | x <- xList] -- For Mandelbrot set
--row y = C.pack [frac ((-0.1) :+ (0.8)) (x :+ y) 0 | x <- xList] -- For Julia set

etaFraction :: Complex Double -> Double
etaFraction z = (log (log (magnitude z))) / log 2

smoothEta :: Int -> Complex Double -> Double -- Smooth escape time algorithm value
smoothEta iter z = (fromIntegral iter - etaFraction z) / fromIntegral maxIter

color :: Int -> Complex Double -> Double -- Gets the color for the point, in range [0, 1]
color iter z = 1 - smoothEta iter z -- Smooth escape time algorithm (and invert)
--color iter z = fromIntegral iter / fromIntegral maxIter

interpolate :: Double -> Char -- Adds an interpolation curve for interpolating color
interpolate v = chr (truncate ((v ^ 12) * 255)) -- Polynomial curve
--interpolate v = chr (truncate(v * 255)) -- Linear

frac :: Complex Double -> Complex Double -> Int -> Char -- The actual fractal algorithm!
frac c z iter
     | iter >= maxIter = chr 255 -- never escaped, return color value of 255
     | otherwise = let z' = z * z + c
               in if ((realPart z') * (realPart z') + (imagPart z') * (imagPart z')) > 4
                  then interpolate (color iter z')
    else frac c z' (iter + 1)


-- The file output
pgmHeader :: C.ByteString
pgmHeader = C.pack ("P5\n" ++ (show width) ++ " " ++ (show height) ++ "\n255\n")
main = C.writeFile "fractal.pgm" (C.append pgmHeader (C.concat [(row y) | y <- yList]))

And some pictures from this program (each image has had some constants tweaked):


The Mandelbrot set in pure black and white; only pixels considered "inside" the set are black.


A Julia set based on (0.285, -0.01i). I actually edited the brightness/contrast in post-processing on this one to really bring out the spirals.


A zoomed in portion of the Mandelbrot set. I edited this one's brightness/contrast settings in post processing to bring out some of the little details.

Sunday, February 24, 2013

Tweener Demo

This is the second Android app I ever made, and I'm quite happy with it. Its focus is a custom view/control I made, named TweenerControl. As the name (hopefully) implies, it allows you to create tweening/easing curves that can later be used for a variety of things, including view animations. The TweenerControl creates a Tweener object, which implements the android.view.animation.Interpolator interface.

The motivation for the project was to be able to visually and interactively create tweening curves that could be used for a wide range of things. There are various interpolators offered by Android, but it's hard to visualize exactly what an AnticipateInterpolator really looks like, for example.

My tweener works by creating a series of cubic curves and connecting them into a single spline. At first I looked into using cubic Hermite splines, Bézier curves, and Kochanek-Bartels curves, but all of these were problematic because they provide two output values from one input value, and tweening functions need to produce one output value from one input value. Overall, I'm happy with the results the cubic curves give.

For the curious, the cubic curves were generated as follows: a cubic function f(x) is generated from two end points and the slope at the end points. That is, given two points, (x1, y1) and (x2, y2) that lie on the function f(x), and the slope at these points (m1 = f'(x1) and m2 = f'(x2)), the cubic function f(x) = ax3 + bx2 + cx + d is constructed satisfying these constraints. It's just a matter of a little linear algebra. A series of cubic curves are then pieced together into a single spline, representing the overall function that maps inputs in the range [0, 1] to a "tweened" output.

I'll work on getting the code repository online somewhere. Note that I will not be putting this on the Google Play Store, simply because it's a little demo and not something I think should clutter the Play Store.

In the Tweener Demo, there is a box along the top that moves back and forth along the top of the screen according to the curve. Different color schemes can be selected, and the color transitions according to the curve as well. There's a vertical line that moves across the curve as the box moves, showing you which part of the curve is causing the box to be at its specific location. Control points can be added by double-tapping the curve. A control point can be deleted by double-tapping the it. Here's some screenshots:


The initial screen.


The "Android" theme selected, with the curve moved around a bit.


The "Dark" theme selected, with the view zoomed in on a part of the curve.


The "Colorful" theme selected, with extra control points added.

Tuesday, February 5, 2013

Macs are PCs

I know. I know what people mean when they say "Mac" and "PC." But it's still wrong. "PC" is an abbreviation for "Personal Computer," which yes, really is as vague as it sounds. Your MacBook Pro is a personal computer, your iMac is a personal computer, your Dell Inspiron is a personal computer, your Chromebook is a personal computer, etc. They're all PCs. Please, for the love of all that is technically correct, stop pretending like Macs are something different and special from a PC.

If you're going to compare, say "Mac" and "Windows" or "Mac" and "Windows PC." "Mac" is a bit confusing, because it's the name of an operating system and also a product line of personal computers. If you're going to compare operating systems, say "Mac" and "Windows," seeing as they're both operating systems and now you're comparing software to software. Or better yet, say "OS X" and "Windows" because these days, the proper name is "OS X" and not "Mac OS X".

If you want to compare Macintosh (marketed as Mac) personal computers against Windows based personal computers, say "Mac" and (something like) "Windows PC." Without the "Windows," comparing Macs to PCs is like comparing apples to fruits... apples are fruits (think about it: if you were to say "Apples are way better than fruits," you'd sound like an idiot). Specifying "Windows" means now the comparison is specific and actually makes sense.