January 27, 2011

iOS Toolbar Pagecurl Icon

iOS 4.x's page curl UIBarButtonItem comes in blue, and blue, and blue. If you set a UIToolbar's tintColor, then it disappears for some reason.

So I had to make a replacement. Since I use a lot of icons made and given away by awesome people, I feel it is only fair to contribute my share, small though it maybe.


Released under WTFPL. Though it would be nice to hear from you if you do use it :)

Cheers,
Steve

iPhone 4 layer export script for Gimp

I often mockup iPhone interfaces on the Gimp, then exporting each layer for use as backgrounds. For iOS 4 each image needs to be exported twice: one at full resolution with @2x in its filename, and once at half-resolution without the @2x. e.g. nav_bg@2x.png and nav_bg.png.

Exporting layers by hand gets old real quick. Thankfully the Gimp supports scripting in my favourite language: python. Even better, Chris Mohler had already written a script to do something very similar, so all I needed to do was make some simple adjustments:
#!/usr/bin/env python
# -*- coding: <utf-8> -*-
# Author: Chris Mohler <cr33dog@gmail.com>
# Copyright 2009 Chris Mohler
# "Only Visible" and filename formatting introduced by mh
# License: GPL v3+
# Version 0.4
# GIMP plugin to export layers as PNGs
# modified by Shuning Bian 2011

from gimpfu import *
import os, re

gettext.install("gimp20-python", gimp.locale_directory, unicode=True)

def format_filename(layer):
    layername = layer.name.decode('utf-8')
    filename1 = layername + "@2x.png"
    filename2 = layername + ".png"
    return filename1,filename2

def get_layers_to_export(img):
    layers = []
    for layer in img.layers:
        if layer.visible:
            layers.append(layer)
    return layers

def export_layers(img, path):
    dupe = img.duplicate()
    savelayers = get_layers_to_export(dupe)
    for layer in dupe.layers:
        layer.visible = 0 
    for layer in dupe.layers:
        if layer in savelayers:
            layer.visible = 1

            if layer.mask:
                pdb.gimp_layer_remove_mask(layer, 0)

            filename1,filename2 = format_filename(layer)
            fullpath = os.path.join(path, filename1);
            tmp = dupe.duplicate()
            pdb.file_png_save(tmp, tmp.layers[0], fullpath, filename1, 0, 9, 1, 1, 1, 1, 1)

            imgwidth = pdb.gimp_image_width(img)
            imgheight = pdb.gimp_image_height(img)
            pdb.gimp_image_scale_full(tmp, imgwidth/2, imgheight/2, 2)

            fullpath = os.path.join(path, filename2)
            pdb.file_png_save(tmp, tmp.layers[0], fullpath, filename2, 0, 9, 1, 1, 1, 1, 1)

        dupe.remove_layer(layer)

            
register(
    proc_name=("python-fu-export-layers-iphone4"),
    blurb=("Export visible layers as PNG"),
    help=("Export all visible layers as individual PNG files."),
    author=("Chris Mohler <cr33dog@gmail.com> + sbian"),
    copyright=("Chris Mohler"),
    date=("2009"),
    label=("as _PNG"),
    imagetypes=("*"),
    params=[
        (PF_IMAGE, "img", "Image", None),
        (PF_DIRNAME, "path", "Save PNGs here", os.getcwd()),
           ],
    results=[],
    function=(export_layers), 
    menu=("<Image>/File/E_xport Layers"), 
    domain=("gimp20-python", gimp.locale_directory)
    )

main()

Cheers,
Steve

January 23, 2011

Socket.IO Framing Protocol

Socket.IO is a great library and framework that doesn't seem to have a good description of the protocol available. In interest of helping others when they google "socket.io framing protocol", this post was born.

The description of the framing protocol used by Socket.IO below is taken from ajaxorg's Socket.IO-node fork on github. I have made some changes with regards to the annotations, specifically that they must be terminated by with newline. This description is current for v0.6+.

Socket.IO Framing Protocol

Socket.IO 's framing protocol is on top of the underlying transport. Messages have the following format:
(message type)":"(content length)":"(data)","
Where message type is one of:

  • 0 for forced disconnect - no data is sent, always sent as 0:0:,
  • 1 for messages - see below
  • 2 for heartbeats - data is an integer, e.g. 2:1:0, 
  • 3 for session ID handshake - data is a session ID, e.g. 3:3:253,


Messages

If you were familiar with the Socket.IO protocol you will notice there appears to be no way now to specify a message contains JSON data as the new protocol has no counter part to the JSON Frame indicator ~j~. Fear not - the new Socket.IO protocol provides a means for this and many other possible extensions via annotations which maybe attachd to messages in a manner reminiscent of HTTP headers. 

A Socket.IO message now has the following format:
[key[:value]["\n"key[:value]]["\n"key[:value]..."\n"]":"[message]
In words: annotations are key-value pairs where the value may be omitted. Individual annotations are delimited by newlines, and annotations are separated from the message by another newline.

From examining the Socket.IO source, if no annotation is present then there should be no newline, but the colon is still required.

A JSON message is sent with a single annotation with key of j and no value. Below is an example taken from a debugging session:
1:128:j
:{"method":"create_user","params":[{"password":"jhhgffufuhgv","email":"h@h.com"}],"id":"83BCA0A8-7F9D-428A-A546-2EFCBDC20AB3"},
Note there is a newline after the j. The official Socket.IO client uses an additional annotation: r for realm. e.g.
1:18:r:chat
:Hello world,
Note the newline after chat.

Cheers,
Steve

January 16, 2011

UIGraphicsBeginImageContext on iPhone 4

I generate UIImages in some of my iOS applications, and prior to the iPhone 4 they all worked fine. With the introduction of the iPhone 4 and Retina Display, I realised my generated images were blurry even though I am rendering them at twice the resolution.

It turns out this is due to the scale property of my generated UIImages being set to 1. This is mainly due to my use of UIGraphicsBeginImageContext(), which creates a bitmap context that is a) non-opaque and b) scale=1. The fix is quite simple - use UIGraphicsBeginImageContextWithOptions() and specify the scale appropriately.

For images derived from an existing image, e.g. rotating an existing image, it is sufficient to use the scale property of UIImage. For images created "from scratch", I use the following bit of code:
if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)])
{
  UIGraphicsBeginImageContextWithOptions(size, NO, [UIScreen mainScreen].scale);
}
else UIGraphicsBeginImageContext(size);

Cheers,
Steve