JPEGのヘッダーを探訪する
JPEGのバイナリフォーマットは案外シンプルで、けっこう簡単に分解することができます。
JPEGはKey-Valueデータの羅列です。
0xFF
で始まる2バイトはマーカといい、一まとまりの値が格納されていることの印となっています。
例えば0xFF 0xDB
の2バイトはDQTマーカといい、次のマーカまで続くデータ列をDQT(量子化テーブル)として解釈することができます。
JPEGにはこのような構造で、圧縮に使った情報(テーブル)と、画像の圧縮済みデータが格納されています。
圧縮に使った情報としては、DQT(量子化テーブル)
とDHT(ハフマンテーブル)
が格納されており、両方とも伸長するためには不可欠です。
圧縮済みの実データはSOS(スキャンヘッダ)
に続くデータ列に格納されています。
典型的なJPEGは次のような構造をしています。
マーカ | マーカ名 | 意味 |
---|---|---|
0xFF 0xD8 |
SOI | JPEGファイルデータの開始 |
0xFF 0xDB |
DQT | 量子化テーブル |
0xFF 0xC0 |
SOF0 | 圧縮の種類や画像サイズなどの情報 |
0xFF 0xC4 |
DHT | ハフマンテーブル |
0xFF 0xDA |
SOS | 画像データの開始 |
0xFF 0xD9 |
EOI | JPEGファイルデータの終了 |
実例
# SOI - 画像データ開始 FF D8
# DQT - Define Quantization Table(s) http://hp.vector.co.jp/authors/VA032610/JPEGFormat/marker/DQT.htm # 132 bytes FF DB: 00 84 00 0D 09 0A 0B 0A 08 0D 0B 0A 0B 0E 0E 0D 0F 13 20 15 13 12 12 13 27 1C 1E 17 20 2E 29 31 30 2E 29 2D 2C 33 3A 4A 3E 33 36 46 37 2C 2D 40 57 41 46 4C 4E 52 53 52 32 3E 5A 61 5A 50 60 4A 51 52 4F 01 0E 0E 0E 13 11 13 26 15 15 26 4F 35 2D 35 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F 4F
# SOF0 - Start Of Frame (baseline) http://hp.vector.co.jp/authors/VA032610/JPEGFormat/marker/SOF.htm # 17bytes FF C0: 00 11 08 00 28 00 1E 03 01 22 00 02 11 01 03 11 01
# DHT - ハフマンテーブル定義 http://hp.vector.co.jp/authors/VA032610/JPEGFormat/marker/DHT.htm # 418bytes FF C4: 01 A2 00 00 01 05 01 01 01 01 01 01 00 00 00 00 00 00 00 00 01 02 03 04 05 06 07 08 09 0A 0B 10 00 02 01 03 03 02 04 03 05 05 04 04 00 00 01 7D 01 02 03 00 04 11 05 12 21 31 41 06 13 51 61 07 22 71 14 32 81 91 A1 08 23 42 B1 C1 15 52 D1 F0 24 33 62 72 82 09 0A 16 17 18 19 1A 25 26 27 28 29 2A 34 35 36 37 38 39 3A 43 44 45 46 47 48 49 4A 53 54 55 56 57 58 59 5A 63 64 65 66 67 68 69 6A 73 74 75 76 77 78 79 7A 83 84 85 86 87 88 89 8A 92 93 94 95 96 97 98 99 9A A2 A3 A4 A5 A6 A7 A8 A9 AA B2 B3 B4 B5 B6 B7 B8 B9 BA C2 C3 C4 C5 C6 C7 C8 C9 CA D2 D3 D4 D5 D6 D7 D8 D9 DA E1 E2 E3 E4 E5 E6 E7 E8 E9 EA F1 F2 F3 F4 F5 F6 F7 F8 F9 FA 01 00 03 01 01 01 01 01 01 01 01 01 00 00 00 00 00 00 01 02 03 04 05 06 07 08 09 0A 0B 11 00 02 01 02 04 04 03 04 07 05 04 04 00 01 02 77 00 01 02 03 11 04 05 21 31 06 12 41 51 07 61 71 13 22 32 81 08 14 42 91 A1 B1 C1 09 23 33 52 F0 15 62 72 D1 0A 16 24 34 E1 25 F1 17 18 19 1A 26 27 28 29 2A 35 36 37 38 39 3A 43 44 45 46 47 48 49 4A 53 54 55 56 57 58 59 5A 63 64 65 66 67 68 69 6A 73 74 75 76 77 78 79 7A 82 83 84 85 86 87 88 89 8A 92 93 94 95 96 97 98 99 9A A2 A3 A4 A5 A6 A7 A8 A9 AA B2 B3 B4 B5 B6 B7 B8 B9 BA C2 C3 C4 C5 C6 C7 C8 C9 CA D2 D3 D4 D5 D6 D7 D8 D9 DA E2 E3 E4 E5 E6 E7 E8 E9 EA F2 F3 F4 F5 F6 F7 F8 F9 FA
# SOS - スキャンヘッダー http://hp.vector.co.jp/authors/VA032610/JPEGFormat/marker/SOS.htm FF DA: 00 0C 03 01 00 02 11 03 11 00 3F 00 C0 9E C3 50 9F 54 9E 4B 16 91 22 75 52 C7 61 23 9E 0F 38 C0 3C 56 54 96 97 C2 47 F2 ED 5D A2 2C 76 E1 03 02 3B 7B D7 A4 F8 7A 28 FF 00 E1 13 9E E0 5D 3C AC 15 A5 66 60 06 70 0F 04 7B 55 0B 2B 75 16 F1 03 D9 40 FC 71 51 49 CA 4A CD EC 54 DA 4C E1 ED E3 9D 67 4C DA 15 C1 07 3E 41 1C FD 6A E7 8A EC 60 B2 86 C2 68 90 AC B3 40 AD 2A B1 38 2C DC E4 7A 74 AE E1 6D 40 20 80 6B 96 F8 96 71 A8 43 10 E8 8A AB F9 28 FF 00 E1 13 9E E0 5D 3C AC 15 A5 66 60 06 70 0F 04 7B 55 0B 2B 75 16 F1 03 D9 40 FC 71 51 49 CA 4A CD EC 54 DA 4C E1 ED E3 9D 67 4C DA 15 C1 07 3E 41 1C FD 6A E7 8A EC 60 B2 86 C2 68 90 AC B3 40 AD 2A B1 38 2C DC E4 7A 74 AE E1 6D 40 20 80 6B 96 F8 96 71 A8 43 10 E8 8A AB F9 28 FF 00 1A A6 E4 9D AF A0 2B 34 6B E9 57 46 2F 08 B5 B6 06 6E 56 48 C9 1E ED B6 B6 E1 8D 30 3F C2 B0 A7 86 2B 2B 4B 1B 75 66 28 24 EA 7A 9C 1C 9F E5 5A 76 F7 23 6A 65 D7 27 D5 48 A8 C3 6B 16 C5 5F E2 B1 6D 64 65 61 90 D8 3D 7E 5E 95 42 FA DA CF 56 BA B9 96 74 DC 04 E4 2E 47 A0 02 AC 7D A9 4A 02 0C 44 6E C7 0F 54 20 93 75 AB C8 38 DD 71 2F E8 D8 FE 95 18 BD 21 74 6B 84 5C D3 B1 47 5D B9 8E DE E2 C5 66 91 23 51 BC 82 DD 3F CF 34 FB 2B E8 A6 8C 32 CF 6E 58 8E 40 6C F3 58 FE 3F FF 00 59 67 F4 6A CF F0 FF 00 41 55 87 76 A4 88 AD F1 9D 73 7D A4 22 0F 32 29 36 B7 52 BD 45 41 65 6E F3 E9 11 B0 99 E3 6F 3A 52 76 74 39 62 6A D2 7D C5 A4 D2 7F E4 0D 1F FD 74 6F E6 6A 31 6E D0 46 D8 6F 8C
# 終端 FF D9
tips
DQTはqに依存
JPEGのqによって、DQTは画像によらず一意に決まります。
DHTはほとんど同一
ハフマン符号化の性質上、本来はエンコード時に画像データを解析して最適のハフマンテーブルを作るべきです。しかし、処理量が大きいため Standard Huffman Table
としてGeneral Purposeに使えるハフマンテーブルがJPEGの標準として定義されています。
最適化したハフマンテーブルを使うオプションを明示的に指定しなければ、この標準ハフマンテーブルが使用されます。
ですから、大部分のJPEGはqにも画像にもよらずDHTは同一です。
参考文献
iCloudのユーザIDを使って、端末を移行したユーザをセキュアに紐付ける
アプリのデータを新しいiPhoneに移行したい!というのはよくあるニーズだと思います。 単純にはiCloud Documentsを使ってアプリを作っていれば良いのですが、デバッグのしやすさなどの兼ね合いから自社サーバを使っているところが大半だと思います。
データの移行のためには安全にIDを紐付ける必要があります。 広く用いられているのはemail, passwordを使ったアカウントや, 各種SNSでのSSOを使った紐付けかと思います。
しかしながら、一つに事前にユーザにアカウントを作ってもらってから移行してもらうことの運用の難しさです。 パスワードというのは簡単に忘れるものであり、そのパスワードを送るためのメールアドレスでさえ変わったり、忘れたりすることがしょっちゅうです。
二つに日本においてはほぼ確実に間違いなくアカウントを所持しているSNSは存在しません。LINEくらいでしょうか。そのLINEでさえも、IDの紐付けはわりと無頓着であり、携帯を替えるたびにLINEアカウントが変わるのはしょっちゅうです。 紐付けたSNSがなんだったのかを忘れてしまうこともしょっちゅうですね。
他には、電話番号認証は良い手段ですが、実は電話番号は廃止後使いまわせてしまうので、セキュアに紐付けることはできません。
他にも、QRコードなどを使って認証情報を移行するのも一つの手段ですが、実は確実ではありません。なぜなら、携帯を変えるときは古いスマホをその場で下取りに出してしまうことが多いからです。
iPhone -> iPhone の移行なら Apple ID が使える?
上述の問題が全て解決されるわけではありません。しかしながら、アカウントを移行できる可能性を一つでも上げるため、サポート用のロジックを用意することを考えます。
iPhoneを使っている人は新しい携帯もiPhoneであることがほとんどでしょうから、Apple IDを使えば紐付けは可能ではないでしょうか。
基本的には最もベーシックであるEmail+Passwordを使い、Apple IDを使った紐付けでID, Password忘れ防止など利便性を向上することができれば良いですね。
結論から言えば、iCloudの提供するCloudKitを使えば紐付けが可能です。
CloudKitを使ったアカウント紐付け
鍵となるのはCloudKitのCKContainer - fetchUserRecordIDWithCompletionHandler:
です。このメソッドはiCloudユーザに一意のユーザレコードを取得します。
ここで取得したレコードの - recordName
は33文字の文字列IDとなっています。このIDは特定アプリのなかのiCloudユーザに一意です。ですから、他のアプリで同一ユーザが- recordName
を取得したとしても、それは違う値になります。
取得できるのは次のような文字列であり、次のようなコードで取得することができます。
_cd2f38d1db30d2fe80df12c89f463a9e
[[CKContainer defaultContainer] fetchUserRecordIDWithCompletionHandler:^(CKRecordID *recordID, NSError *error) { if (error != nil) { NSLog(@"%@", error); return; } NSLog(@"%@", recordID.recordName); }];
このIDは36^32
通りの名前空間がありますから推論は極めて困難ですが、もう一段階ロックを付ければよりセキュアです。
CloudKitにはUserのみにアクセスできるPrivate Containerがあります。
このPrivate Containerに十分長いキーsecret
を保存しておけば2つの十分長いトークンで認証を行うことができ、セキュアです。
フローとしては、次のようになります。
- (端末A) アプリは
recordID.recordName
が取得できたら、十分に長いキーsecret
を生成しPrivate Containerに格納します。 - (端末A) アプリは自社サーバに
recordID.recordName
,secret
を送信します。 - (端末B) 携帯を移行してアプリを起動したとき、
recordID.recordName
を取得します。また、secret
をPrivate Containerから取得します。 - (端末B) これらの値を自社サーバに送信し、(2)で送ったものと同一かどうかを確かめます。
- (4)が成功すれば、端末Aと端末Bは紐付けされ、同一ユーザとみなすことができます。
コードの注意点としては、secret
の書き込みはいわゆるFindOrInsert処理になります。
ASImageNodeのすゝめ
ASImageNode
は、Facebookのオープンソース非同期UIライブラリAsyncDisplayKit
に含まれるUIImageView
相当のコンポーネントです。
このコンポーネントを使用することで、かんたんに画像表示のパフォーマンスを改善できることをご紹介します。
UIImageView の問題点
UIImageView
は画像を表示するのに必要なすべての処理をメインスレッドで行ってしまいます。画像を表示するための処理は大きなバイナリデータを扱うため、非常に重たい処理です。メインスレッドでこれを行うと簡単にUIをブロックしてしまいます。
表示する画像が重かったり、または UICollectionView
などで画像を表示していると、顕著に感じられると思います。
対して、ASImageNode
は画像を表示するための下準備すべて(デコード、レンダリング)をバックグラウンドスレッドで行います。メインスレッドは ASImageNode
よりレンダリング済みのデータを受け取り、View にベタッと貼り付けるだけ。メインスレッドの処理量が大幅に削減されています。
実際の効果(続報あり?)
いまのプロジェクトでは、4列のUICollectionView
に200x200ほどの画像を順次 - setImage:
するだけで20-30fps程度まで下がってしまっていました。
体感的にも、カクカクしています。
今回、ASImageNode
を使って描画を行うことで、60fps付近をキープするようになりました。
体感的には、スクロールでカクカクを全く感じなくなりました。
コード
UICollectionView でのコードを掲載します。
// KIExampleCollectionViewCell @interface KIExampleCollectionViewCell () @property ASImageNode *imageNode; @end - (void)prepareForReuse { [super prepareForReuse]; // (1) で追加した View を消す。`UIImageView-setImage:nil` の代わり。 [_imageNode.view removeFromSuperview]; } - (void)setImage:(UIImage *)image { if (image == nil) { return; } dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ if (!_imageNode) { _imageNode = [[ASImageNode alloc] init]; _imageNode.frame = self.contentView.frame; } _imageNode.image = image; dispatch_async(dispatch_get_main_queue(), ^{ // (1) _imageNode.view で描画結果の View を取得できます。 [self.contentView addSubview:_imageNode.view]; }); }); }
Background Fetch が起きたあと、アプリを起動しても didFinishLaunching を通らない
Background Fetch を使うと、アプリのライクサイクルイベントが少し変わってくるのでメモ書きをします。
通常のアプリ起動時のライフサイクルイベント
- application:didFinishLaunchingWithOptions:
- applicationDidBecomeActive:
こののち、「アプリを閉じて再度開く」「写真へのアクセス許可など OS ダイアログが閉じられる」などが起こると、-applicationDidBecomeActive:
のみが呼ばれます。
メモリが不足するなどしてプロセスがキルされない限り、- application:didFinishLaunchingWithOptions:
は二度と呼ばれないことに注意してください。
Background Fetch が起こり、その後アプリを起動した場合
- application:didFinishLaunchingWithOptions:
(Background Fetch が起こったとき)- application:performFetchWithCompletionHandler:
- applicationDidBecomeActive:
(アイコンをタッチして、アプリを起動したとき)
Background Fetch が起きたときは、ユーザがアプリを開くなどしなくても - application:didFinishLaunchingWithOptions:
が呼ばれることに注意してください。なお、この際 launchOptions
には nil
が渡されます。
取り扱いに注意すべきこと
didFinishLaunching
や performFetch
での KeyChain の取り扱いに注意
KeyChain は applicationState
によって値が取り出せずエラーになることがあります。(設定次第です)
performFetch
では気をつけていても、didFinishLaunching
でユーザIDをトラッキング用に取得するなどして意図せず値が取り出せない状況が起こり得ます。
例外は起きませんが、適切にエラーハンドリングをする必要があります。
たとえば、「値が取り出せない場合は新しく割り当てる」といった処理をしている場合は、Background Fetch が起きるたびに値が変わってしまいます。
対策としては、KeyChain の accessibility として kSecAttrAccessibleAfterFirstUnlock
を使うか、値を取得するときに起こりうるエラーを適切にハンドリングします。
Gilt: iOS7でbackground fetchを利用するとログアウトしてしまうバグへの対応 - ワザノバ | wazanova
ユーザがアプリを明示的に起動しなくても didFinishLaunching
が呼ばれることに注意
Background Fetch をつかうと、ユーザが明示的にアイコンをタップしてアプリを起動しなくても、- application:didFinishLaunchingWithOptions:
が呼ばれてしまいます。
このとき、メソッド内で「アプリ起動イベント」などを計測していると、誤ったデータが取得されてしまいます。
- applicationDidBecomeActive:
を「アプリ起動イベント」として数えるほうが正確です。この場合、「アプリを閉じてすぐ再度アプリを開く」「写真へのアクセス許可など OS ダイアログが閉じられる」といった状況でも「アプリ起動イベント」として計測されてしまうことに注意します。
アカウント移行用に、iCloud アカウントに紐付いた一意の ID を取得する
複数の iOS 端末でのアカウント共有をしてもらうため、iCloud アカウントの ID を利用することを考えます。
様々な方法がありますが、CloudKit を使うのが一番簡単で考えることも少なくラクだと思います。
事前準備
- iCloud Capability を ON にします。あわせて、CloudKit にチェックを入れます。
Capability の変更がありますが、iCloud の場合は既存のアプリからアップデートしても正常に Capability が移行されます。
コード
次のようにして1.iCloudアカウントに一意で2.アプリ毎に異なるIDを取得することができます。(トークン置換攻撃が予防されてます)
[[CKContainer defaultContainer] fetchUserRecordIDWithCompletionHandler:^(CKRecordID *recordID, NSError *error) { if (error != nil) { NSLog(@"%@", error); return; } NSLog(@"%@", recordID.recordName); // recordID.recordName が ID }];
このコードを実行すると、_cd2f38d1db30d2fe80df12c89f463a9e
のような NSString
が取得できます。この ID の実体は UUID であり、推測可能性や名前空間の大きさは UUID に準じます。
一回の fetch には 2 秒ほどかかり、自動的にキャッシュされることもありません。したがって、KeyChain などを用いてキャッシュする運用が必要になります。
iCloud アカウントにログインしていない場合は、error のみが返ります。 AppStore でアプリをインストールしていることを考えると、このケースは数は多くないように思います。
全体的な性質を鑑みると、ユーザ ID としては自社のものを用意し、こちらの iCloud ID はアカウント紐付け用に利用するのがよいと思います。
参考
iOS Onboarding without Signup Screens — welcome aboard — Medium
「リアクティブ」ほんとに要る?
リアクティブは値の変化に対して、対応した処理を動かせることができる。これは直感的にはとても合理的であり、キレイな対応となっている。
実際、リアクティブに書くと処理が重複しにくかったり、値の変化に対して起こる動作がわかりやすく整理される、といった効果がある。
しかしながら、実際的にはリアクティブよりも地道な一本道コードのほうが最終的には保守しやすい、という話。 というものを書こうとしたけれども面倒になったので見出しだけ。
- 値に対してリアクティブに動作をさせないほうがいい
- ほんとうに、その動作は値に対してリアクティブか?
- 動作はアクションに紐付いている
- 真に値にリアクティブなのは Computed Value (Cache など) くらいでは?
- リアクティブなコードは暗黙知の温床
- コードから動作が「追えなく」なる
- 汚いコードより、非同期に動きまくるコードのほうがつらい(そもそも追えない)
- 暗黙知をより多く要求する
- というか本人しか読めない
- 前提知識なく読みやすいのはアクションから入り、一本道に処理が書かれるコード
- なんだかんだで、泥臭く一本道に実装したほうが良い
- 処理が重複したり、あちこちで状態もったりするのは設計/書き方で解決すべき話です。
ところで、なぜこのような結論(感想?)になるのかというと、既存のプログラミング言語が上から下に書かれ、処理もまたそうであるからです。
つらい。
Medium方式のプログレッシブ画像読み込みの予備調査
Medium は 30x30pixel の画像を表示しておいてから、原寸画像を表示しているらしい。
そこで、30x30pixel というのがどの程度のクオリティ、サイズなのかを調査してみた。
iPhone 5S で撮った写真を題材にします。
原寸
2448 × 3264, 1.8MB
30x30 画像
30 × 40, 7KB (8016 bytes)
単純にリサイズするだけでは 7KB もあり大きさにしてはちょっと重い。
ここまで小さいと画像のクオリティも不必要なので色情報を落とすことにする。
30x30 画像(gif)
色を落とすのに一番楽な gif にしてみた。ここまですると 2KB.
30 x 40, 2KB (2146 bytes)
dataURL 形式
gif 画像を dataURL にし、html に直接埋め込めるようにしてみる。
2866 bytes
コード
画像スワップのもっとも単純なコードは次のような感じ
document.addEventListener("DOMContentLoaded", function() { Array.prototype.forEach.call(document.querySelectorAll("img"), function(el) { var originalSrc = el.getAttribute("data-original"); if (!originalSrc) return; var shdwImg = new Image(); shdwImg.src = originalSrc; shdwImg.onload = function () { el.src = originalSrc; }; }); });
↑リロードしてみてください(Shift + F5 でも!)
追記
Color reduction + jpegoptim + quality down で 30x40 px で 716 bytes まで落ちました。 1,200pixel ありますから、そんなものでしょうか。 ちなみに Color reduction は適当に 256 色に減色しました。
1pxあたり4-5bit程度になりました。 JPEGですから単純には言えませんが、色空間をもっと絞れば軽くなるでしょう… クライアントがアプリならJPEGのヘッダーまるまる落とすことも可能です。
Base64 956bytes
ちなみに Base64 の結果を deflate すると 677 bytes になります
eJylkEvPsjoUhX+QgyJykcEZtFCl3JQCCswEBAGVl4u0+us/zJec5LzT02Sne6dP1l6rQGuABKEftKlFK4igD5dpuaEBRIYMCDsTscpEVUd0WDl61TnGrfKNSvCMm3Da3XBC+PZu6aQkNX7Yn3y26lawA1y7GHPPaNVjI62sQMZB5G4DI9eOzfxX2+8cnUmLDvMxYqqtk+7wGdX/VzNgEFJ9MQ6RZMCAQHjDCHKMwNaH+2++yl3y/T4RRlVufBmHfxmCICO67vvGv4hPDIQwRJTcXD9FmPt1FO9Rl5kI8eSdpCYVZrAA1fKPGEL2e4e72ML4u8Pky7sfLT6I8R/EhzpkPiWUNG7jUaB10IBfv4QuGeCR6YNY/Gwum7no4+x5r28tPee1Ox5mvzno8Ppo28nIuQ263m383YeR2oCDbB5zzbtoUjTwcgw+2U4MijuVN+ldl5khkO1YoJ900rxTz/rS2sWyJORWLiUhs+VF1vH60ftxgrxHoQFl7UwUYEaA8+mUZZfVWkwS8XzhZyqvkycqqrBN1eIlTta56hIMNS8atGTPXyMmiO/r7qiwCihr6lSScXV8NtllZl29kunaEcoxvtfpTQAtMFXz1AycSmYG9v0nGZia+qLYNaOQ3JVWE9mGgOiSr7NO4tXbeR+uivuiK49GCrun6epqX8ePrWyrDX72VzML5rw/b1RQvEKRrC61wOLWLSq08gKwdAlxLZSZ4xuowjCEuRK1TMnVXCxbbkrCSW2vjRZGVA6SaNqGMbnc5ywaQ8rt+WePyulphqqiZMVr9B7WOfhEjjtPKEaHsnnSbl1Eso5zSbvFBYhPTQMf1UPyNnbB1Tk/Dr03aUGwHl1nSNo6P1t1SbXmnT8/wsUzywNtdkIExhho7T9/APAjNFk=
WebPにすると492bytesにもなりました。 Base64 -> deflate で 515 bytes の文字列に出来ます(ASCII)
UklGRs4BAABXRUJQVlA4IMIBAADQCACdASoeACgAPp1GnUslo6KhpWswsBOJYgCdMwNtvSYiyvtM w56Ala3goMH6PLiFhzia0QdinyIhepGlg8ibpRNrfC1Nl3pkkmttCnnAAP7qXRTmseb0z2b0x2nq LIVjG0hhDqxf9Wfk8TDunPCdwd5mVkDjOH0j/QRIdp2y1UYdvdTVHe0LU0WZSv3aURxWNVRdqjpp wnDo9T+jga2qtPXFffebScNv6xxA1b5SDFB9GuH0UcCielWXizpEAge3+OI9It23/hZcOmq0JW8O x8iHu0dpMh8681nw0q90Ommw2SJiLg/8bNdlvGg3f+4DieLt/ONMj3Tg7CF8iqh3NT4EwiJ4hsR4 kjiEWcfE/VUFzHKr5QhajTRtuof6WY0mpW+GV7Y6P0akaI/pCdap+uL9hOePenx+kUwqumODIhat 9WrbfvsmPDSNHrKuWoLFPlrLNCDfcc3HCWBdhnCXXkQX4GcxUVk1W1byotQvJprhDxWpfAI79afz dJNJ3TJ6JoCc0eB2lsq0Gzwjwq4EfmBFXIbZbvX2Yoo8Uh/geZx/OfN02oOGZDQzmwezSElxw6V7 NEE9ggqzFYFjrQdAAAA=