function dropfile(ev, target, fn) { ev.preventDefault(); let reader = new FileReader(); reader.readAsArrayBuffer(event.dataTransfer.files[0]); reader.onload = function() { tgt = document.getElementById(target); tgt.value = [...new Uint8Array(reader.result)] .map(x => x.toString(16).padStart(2, '0')) .join(''); tgt.dispatchEvent(new KeyboardEvent('keyup', {'key':'a'})); }; } // General function determine_file_ext(str) { let s8 = str.slice(0,8); if (s8 == '\x89PNG\x0d\x0a\x1a\x0a') return 'png'; if (s8 == '\x00\x00\x00\x0CjP ' || s8 == '\xFF\x4F\xFF\x51\x00\x2F\x00\x00') return 'jp2'; if (str.slice(0,6) == 'bplist') return 'plist'; let s4 = str.slice(0,4); if (s4 == 'ARGB') return 'argb'; if (s4 == 'icns') return 'icns'; if (str.slice(0,3) == '\xFF\xD8\xFF') return 'jpg'; return null; } function icns_type(head) { if (['is32', 'il32', 'ih32', 'it32', 'icp4', 'icp5'].indexOf(head) > -1) return 'rgb'; if (['s8mk', 'l8mk', 'h8mk', 't8mk'].indexOf(head) > -1) return 'mask'; if (['ICN#', 'icm#', 'ics#', 'ich#'].indexOf(head) > -1) return 'iconmask'; if (['icm8', 'ics8', 'icl8', 'ich8'].indexOf(head) > -1) return 'icon8b'; if (['icm4', 'ics4', 'icl4', 'ich4'].indexOf(head) > -1) return 'icon4b'; if (['sbtp', 'slct', '\xFD\xD9\x2F\xA8'].indexOf(head) > -1) return 'icns'; if (head == 'ICON') return 'icon1b'; if (head == 'TOC ') return 'toc'; if (head == 'info') return 'plist'; return 'bin'; } function is_it32(ext, itype, first4b) { if (ext == 'rgb') return itype == 'it32'; if (ext == null) return first4b == [0,0,0,0] || first4b == '00000000'; return false; } function get_length(hex, i) { return Number('0x'+hex.substring(i, i + 8)); } function as_str(hex, i_start, i_end) { var str = ''; for (var u = i_start; u < i_end; u += 2) str += String.fromCharCode(parseInt(hex.substr(u, 2), 16)); return str; } function* parse_file(hex_str) { function get_media_ext(itype, idx, len) { let ext = determine_file_ext(as_str(hex_str, idx, idx + 16)); if (ext || !itype) return [ext, idx, idx + len]; return [icns_type(itype), idx, idx + len]; } var txt = ''; var i = 0; let ext = get_media_ext(null, 0, hex_str.length); if (ext[0] == 'icns') { var num = get_length(hex_str, i + 8); yield ['icns', i, num, null]; i += 8 * 2; while (i < hex_str.length) { let head = as_str(hex_str, i, i + 8); num = get_length(hex_str, i + 8); yield [head, i, num, get_media_ext(head, i + 16, num * 2 - 16)]; i += num * 2; } } else { yield [null, 0, hex_str.length, ext]; } } // Image viewer function num_arr_from_hex(hex) { var ret = []; for (var i = 0; i < hex.length; i += 2) ret.push(parseInt(hex.substr(i, 2), 16)); return ret; } function msb_stream(source) { var data = []; for (var ii = 0; ii < source.length; ii++) { let chr = source[ii]; for (var uu = 7; uu >= 0; uu--) { data.push((chr & (1 << uu)) ? 255 : 0); } } return data; } function expand_rgb(num_arr) { var i = 0; var ret = []; while (i < num_arr.length) { x = num_arr[i]; i++; if (x < 128) { for (var u = i; u < i + x + 1; u++) { ret.push(num_arr[u]); } i += x + 1; } else { for (var u = x - 128 + 3; u > 0; u--) { ret.push(num_arr[i]); } i++; } } return ret; } function make_image(data, channels) { let scale = 2; let per_channel = data.length / channels; let orig_width = Math.sqrt(per_channel); let width = orig_width * scale; let height = width; var canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; var ctx = canvas.getContext('2d'); let map; switch (channels) { case 1: map = [0, 0, 0, -1]; break; case 2: map = [0, 0, 0, 1]; break; case 3: map = [0, 1, 2, -1]; break; case 4: map = [1, 2, 3, 0]; break; } for (var i = 0; i < per_channel; i++) { let r = data[map[0] * per_channel + i]; let g = data[map[1] * per_channel + i]; let b = data[map[2] * per_channel + i]; let a = (map[3] == -1) ? 255 : data[map[3] * per_channel + i]; var imagedata = ctx.createImageData(scale, scale); for (var idx = scale * scale * 4 - 4; idx >= 0; idx -= 4) { imagedata.data[idx] = r; imagedata.data[idx + 1] = g; imagedata.data[idx + 2] = b; imagedata.data[idx + 3] = a; } let y = Math.floor(i / orig_width); let x = i - y * orig_width; ctx.putImageData(imagedata, x * scale, y * scale); } return [canvas, orig_width]; } // Entry point function inspect_into(sender, dest) { function fn(cls, hex, idx, len, tooltip) { var tmp = ''; for (var u = idx; u < idx + len * 2; u += 2) tmp += ' ' + hex[u] + hex[u+1]; let ttp = tooltip ? ' title="' + tooltip + '"' : ''; return `${tmp.substring(1)} `; } let output = document.getElementById(dest); output.innerHTML = 'loading ...'; let src = sender.value.replace(/\s/g, ''); var txt = ''; for (let [head, i, len, ext] of parse_file(src)) { txt += '
'; if (head) { txt += '

' + head + '

'; txt += fn('head', src, i, 4, head); txt += fn('len', src, i + 8, 4, 'len: ' + len); } else { txt += '

raw data

'; } if (!ext) { txt += '
'; continue; } // top icns-header let abbreviate; if (['argb', 'rgb', null].indexOf(ext[0]) > -1) abbreviate = null; else if (ext[0] == 'png') abbreviate = 'PNG data'; else if (ext[0] == 'plist') abbreviate = 'info.plist'; else if (ext[0] == 'icns') abbreviate = 'icns file'; else abbreviate = 'raw data'; if (abbreviate) { txt += `... ${abbreviate}, ${len - 8} bytes ...`; txt += ''; continue; } // parse unpacking let is_argb = ext[0] == 'argb'; var u = ext[1]; if (is_argb || is_it32(ext[0], head, src.substring(u,u+8))) { let title = (ext[0] == 'argb') ? 'ARGB' : 'it32-header'; txt += fn('head', src, u, 4, title); u += 8; } var total = 0; while (u < ext[2]) { x = Number('0x'+src[u]+src[u+1]); if (x < 128) { txt += fn('ctrl', src, u, 1, 'Copy ' + (x + 1) + ' bytes'); txt += fn('data', src, u + 2, x + 1); total += x + 1; u += x * 2 + 4; } else { txt += fn('ctrl', src, u, 1, 'Repeat ' + (x - 128 + 3) + ' times'); txt += fn('data', src, u + 2, 1); total += x - 128 + 3; u += 4; } } let w = Math.sqrt(total / (is_argb ? 4 : 3)); txt += '

Image size: ' + w + 'x' + w + '

'; txt += ''; } output.innerHTML = txt; } function put_images_into(sender, dest) { function get_image_size(data, ext, i) { if (ext == 'png') { let w = get_length(data, i + 16 * 2); let h = get_length(data, i + 20 * 2); return [w, h]; } if (ext == 'jp2') { if (data.substring(i, i + 8).toUpperCase() == 'FF4FFF51') { let w = get_length(data, i + 8 * 2); let h = get_length(data, i + 12 * 2); return [w, h]; } let len_ftype = get_length(data, i + 12 * 2); // file header + type box + header box (super box) + image header box let offset = 12 + len_ftype + 8 + 8; let h = get_length(data, i + offset * 2); let w = get_length(data, i + offset * 2 + 8); return [w, h]; } } function append_img_div(head, typ, w, h) { let div = document.createElement('div'); var desc = typ || '?'; if (w) { desc += ': ' + w + 'x' + h; } div.innerHTML = `

${head || ''}

${desc}

`; output.appendChild(div); return div; } let src = sender.value.replace(/\s/g, ''); let output = document.getElementById(dest); output.innerHTML = ''; for (let [head, , , ext] of parse_file(src)) { if (!ext) continue; if (ext[0] == 'png' || ext[0] == 'jp2') { let [w, h] = get_image_size(src, ext[0], ext[1]); let img = append_img_div(head, ext[0], w, h); img.innerHTML += ``; continue; } if (['argb', 'rgb', 'mask', 'iconmask', 'icon1b', null].indexOf(ext[0]) == -1) { append_img_div(head, ext[0]).innerHTML += `

${(ext[2]-ext[1])/2} Bytes

`; continue; } let num_arr = num_arr_from_hex(src.substring(ext[1], ext[2])); let ch; let data; if (ext[0] == 'argb') { ch = 4; data = expand_rgb(num_arr.slice(4)); } else if (ext[0] == 'mask') { ch = 1; data = num_arr; } else if (ext[0] == 'icon1b') { ch = 1; data = msb_stream(num_arr); } else if (ext[0] == 'iconmask') { ch = 2; data = msb_stream(num_arr); } else { let it32 = is_it32(ext[0], head, num_arr.slice(0,4)); ch = 3; data = expand_rgb(it32 ? num_arr.slice(4) : num_arr); } let [img, w] = make_image(data, ch); append_img_div(head, ext[0] || 'rgb', w, w).appendChild(img); } }