androidbitmappngdecodingclickatell

Image packet decoding on Android


I am trying to request a captcha code via the Clickatell api and show the image on my activity. I manage to get the xml response containing the image, but I can't decode it properly in order to show it (the bitmap created is always null).

The xml response:

<ClICKATELLSDK>
<Action>get_captcha</Action>
<Result>Success</Result>
<Values>
<Value>
<captcha_id>c202a3fc98c50a5f8dccc8540077614e</captcha_id>
<captcha_image>%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%AF%00%00%00-%08%02%00%00%00W%88%AC%F1%00%00%12%1AIDATx%9C%ED%5CyxTU%B2%AFs%97%5E%93%EE%A4%B3%27%84l%24%21%40X%24%A8%04%81%A8++%22H%40% B8l%2C%E7%60%3D%14%27%831%D2%C5Z%E2Fu%08%3Cm3%A9%DB%EB%A2%2C% D2FsZ%83H%2B%7B%13s%0Du%87%D4%A6%F3DE%0E%5Db%7B.a%FFZ%D7T%1E% %06%94%96%89%F4%91%00%D0%9D2%85%1E%1B%FA%01%8A%AB+%E0%FF%00%CDSh%D94%F1%00%D8%00%00%00%00IEND%AEB%60%82
</captcha_image>
<Value>
</Values>
<Timestamp>1316511558</Timestamp>
</ClICKATELLSDK>

I parse this xml and I save the captcha_image to a String that I use in this code to create the bitmap:

captchaImage = <captcha_image from xml>
byte[] encodeByte = Base64.decode(captchaImage, Base64.DEFAULT);
Bitmap bitmap = BitmapFactory.decodeByteArray(encodeByte, 0, encodeByte.length);

Solution

  • First of all, I think Url encoded value pasted above is corrupted. In url encoding, hexedecimal representation must lead after % character. But there is part like this: %1E% %06

    You can use this TextUtil class to encode/decode from/to byte array!

    String xmlValue = <your xml captcha value>
    byte[] decoded = TextUtil.urlDecode(xmlValue.getBytes("UTF-8"));
    
    public final class TextUtil {
    static final String HEX_DIGITS = "0123456789ABCDEF";
    
    protected static String urlEncode(byte[] rs) {
        StringBuffer result = new StringBuffer(rs.length * 2);
    
        for (int i = 0; i < rs.length; i++) {
            char c = (char) rs[i];
    
            switch (c) {
            case '_':
            case '.':
            case '*':
            case '-':
            case '/':
                result.append(c);
                break;
    
            case ' ':
                result.append('+');
                break;
    
            default:
                if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) {
                    result.append(c);
                } else {
                    result.append('%');
                    result.append(HEX_DIGITS.charAt((c & 0xF0) >> 4));
                    result.append(HEX_DIGITS.charAt(c & 0x0F));
                }
            }
    
        } 
    
        return result.toString();
    }
    
    protected static byte[] urlDecode(byte[] bytes) throws UnsupportedEncodingException,
            IllegalArgumentException {
        if (bytes == null) {
            return null;
        }
    
        byte[] decodeBytes = new byte[bytes.length];
        int decodedByteCount = 0;
    
        try {
            for (int count = 0; count < bytes.length; count++) {
                switch (bytes[count]) {
                case '+':
                    decodeBytes[decodedByteCount++] = (byte) ' ';
                    break;
    
                case '%':
                    decodeBytes[decodedByteCount++] = (byte) ((HEX_DIGITS.indexOf(bytes[++count]) << 4) + (HEX_DIGITS
                            .indexOf(bytes[++count])));
    
                    break;
    
                default:
                    decodeBytes[decodedByteCount++] = bytes[count];
                }
            }
    
        } catch (IndexOutOfBoundsException ae) {
            throw new IllegalArgumentException("Malformed encoding");
        }
    
        return decodeBytes;
    }
    }