The Real Zack Morris

Every API is Flawed

Apologists annoy me. As hackers, we make all kinds of excuses for why things are a certain way, and then we route around the damage and encapsulate it away inside our own APIs. John Carmack once said (as I recall) that if a system API is good enough, then we don’t need a library. Libraries (especially open source) exist because of fundamental flaws in operating systems that make them difficult to use. Look around. Libraries are everywhere. Systems suck.

There’s a few reasons for this, but the main one is that since everyone has a different life experience, there will never be a consensus on what should be standard. Some people want speed, some people want readability, some people want small code size, and others like me want it all. I’m always searching for the latest and greatest methodologies.

The second reason, as I see it, is proprietary software. Companies have a financial incentive to create their own ecosystems. This runs counter to trying to create standards. Just look to Objective-C on iOS or how Android originally used java or all of Microsoft Windows to see this in action.

The third reason is human fallibility. No matter how hard you try, your software will always suck upon closer inspection at some future date. This is because technology is always improving, and someday software will be reduced to the minimum number of bits needed to encapsulate the idea that you tell the computer with your voice or brainwaves. The million lines of code limit in large projects is not so much a limitation of the human mind, but the limit of methodologies. Code grows because operating systems evolve and force us to include legacy code. It grows because new eyes look at it and we can’t include our memories with the comments. It grows because methodologies change.

But what really annoys me about all of this is when easy mistakes get lumped in with hard ones. I respect that no API is perfect, but what I have no patience for is programmers who lack the ability to simulate how people will use their code in the real world. When I’m finding major bugs 5 minutes into using an API, then the whole thing is suspect. It’s like trying to read a book written by a child. It’s quaint when the kid is just learning, but when it’s a child emperor and your life depends on telling him what he wants to hear, diplomacy becomes a full time job.

Welcome to mobile app development today. The internet bubble of the 90s had a certain quaintness because it was all so new. But now we are hitting the walls of entrenched dogma. Smalltalk was a cute language that brought some interesting ideas to the table. It’s kind of a half-lisp with its most important features being named parameters and message passing. Unfortunately it never caught on because named parameters turn out to be something that the IDE can provide for you by inference (so there’s really no need to make the syntax more verbose), and message passing turns out to be very difficult to debug, especially with concurrency (something you find out late in the game, which has sparked a debate between the node.js and thread camps). Since Objective-C is a hybrid of smalltalk and c, it inherits the worst features of both languages without bringing much innovation to the table. I think of it more as syntactic sugar than a new language. It was already showing its age by the mid 90s. But it’s somehow the language of choice for iOS development.

You’re probably wondering what any of this has to do with the two images at the top of the page. Well, the code snippet below loads a CGImageRef from disk in iOS. It’s one of the smaller c++ snippets I could come up with, without using an external library. It returns a ptr to the pixel buffer, that can be disposed with free(), and optionally tells the width and height if they aren’t nil. The very first thing I tried with a transparent PNG didn’t work (that’s the first image above). There is a bug in CGDataProviderCopyData() that cause any zero alpha pixels to come out white. That is the code that is commented out. The fix is to manually create a CGBitmapContext and draw the image into it translucently.

Now, I can already hear the apologists telling me that I shouldn’t refer to the raw pixels anyway, because of issues with colorspaces or whatever. Maybe there are problems with resolutions or any number of reasons to draw into a context. Honestly I don’t care. When I ask for the raw data, give me the raw data. Apple actually had to do extra work to do this wrong. My guess is that they allocated a white buffer under the hood and then decompressed the PNG over, skipping fully translucent pixels (as an optimization).

The reason I’m writing this post is because I encounter problems like this NEARLY EVERY TIME I TRY TO DO ANYTHING. In all APIs. Everywhere. I’m not even going to bother reporting this as a bug to Apple because 1) they won’t fix something this entrenched and 2) what I’m complaining about is a problem with THEIR methodology, where they discount developers’ time. They take the time to study how users interact with technology, but not developers. They are punting on this stuff far too often. It’s gotten bad enough that 90% of my workload now consists of working around flawed methodologies instead of writing useful code. I’m coming to realize that this transcends language or operating system. This is a problem with authority mainly, some stubbornness and even ignorance.

Nobody seems to have come up with a good way for APIs to communicate with each other, and I feel that it won’t matter how far languages progress until we deal with this issue. I have an idea how to solve this in my next post. I think the key is to not use language at all, but reexamine the metaphor of an API itself. Since APIs are guaranteed to be flawed for the reasons I talked about, let’s accept that and work on encapsulation. Let’s replace authority with merit.

#include "CoreGraphics/CGDataProvider.h"
#include "CoreGraphics/CGImage.h"
#include "CoreGraphics/CGBitmapContext.h"

void*    LoadImage( const char *path, int *width, int *height )
{
    CGDataProviderRef    dataProvider = CGDataProviderCreateWithFilename( path );
    UInt8                *pixels = nil;
    
    if( dataProvider )
    {
        CGImageRef            image = CGImageCreateWithPNGDataProvider( dataProvider, NULL, false, kCGRenderingIntentDefault );
        
        if( !image ) image = CGImageCreateWithJPEGDataProvider( dataProvider, NULL, false, kCGRenderingIntentDefault );
        
        if( image )
        {
            /*// this doesn't work because of a bug in CGImageRef that renders a PNG image incorrectly so that zero alpha pixels turn out white
            CFDataRef            data = CGDataProviderCopyData( CGImageGetDataProvider( image ) );
            
            if( data )
            {
                int        length = CFDataGetLength( data );
                
                pixels = (UInt8*) malloc( length );
                
                if( pixels )
                {
                    CFDataGetBytes( data, CFRangeMake( 0, length ), pixels );
                    
                    *width = CGImageGetWidth( image );
                    *height = CGImageGetHeight( image );
                }
                
                CFRelease( data );
            }*/
            
            // draw the image transparently onto a CGBitmapContext
            *width = CGImageGetWidth( image );
            *height = CGImageGetHeight( image );
            
            pixels = (UInt8*) malloc( (*width)*(*height)*4 );
            
            if( pixels )
            {
                CGContextRef    context = CGBitmapContextCreate( pixels, *width, *height, 8, *width*4, CGImageGetColorSpace( image ), kCGImageAlphaPremultipliedLast );
                
                if( context )
                {
                    CGContextClearRect( context, CGRectMake( 0, 0, *width, *height ) );
                    
                    CGContextDrawImage( context, CGRectMake( 0, 0, *width, *height ), image );    // now pixels contains image pixels
                    
                    CGContextRelease( context );
                }
                else
                {
                    free( pixels );
                    pixels = nil;
                }
            }
            
            CFRelease( image );
        }
        
        CFRelease( dataProvider );
    }
    
    return( pixels );
}

Discuss on Hacker News

Comments

blog comments powered by Disqus