RetinaImageShrinker ver1.0.3

レティナ用イメージから非レティナ用イメージを作成するフリーウエア「RetinaImageShrinker」を更新しました。
まぁ不具合の修正なんですけどね。。

変更内容
・高さ、幅が1ピクセルのイメージを縮小した時に不正なイメージが生成される不具合を修正

ダウンロードはこちらから

UIImageの一部を切り抜く方法

画像の一部分を切り抜いたイメージを作成する方法です。
画像のトリミングや画像を複数枚に切り出す時などに使用して下さい。


// image : 元の画像
// rect  : 切り抜く範囲
-(UIImage*)clipImage:(UIImage*)image rect:(CGRect)rect
{
    // イメージの解像度に従いrectも換算
    float scale = image.scale;
    CGRect cliprect = CGRectMake(rect.origin.x * scale, rect.origin.y * scale,
                                 rect.size.width * scale, rect.size.height * scale);
    
    // ソース画像からCGImageRefを取り出す
    CGImageRef srcImgRef = [image CGImage];
    
    // 指定された範囲を切り抜いたCGImageRefを生成しUIImageとする
    CGImageRef imgRef = CGImageCreateWithImageInRect(srcImgRef, cliprect);
    UIImage* resultImage = [UIImage imageWithCGImage:imgRef scale:scale orientation:image.imageOrientation];
    
    // 後片付け
    CGImageRelease(imgRef);
        
    return resultImage;
}


@2xなどのratina用イメージにも対応してます。

海王丸パーク

ゴールデンウィークに家でゴロゴロしてばかりなのもアレなので、妻とワンコを連れて富山の海王丸パークへ行ってきました。

日頃の行いが悪いにも関わらず、すかっと晴れて、立山連峰も綺麗に見えていました。
海王丸パーク

先日夜景として撮影した新湊大橋もパシャリ
新湊大橋

この後チューリップフェアに行ったのですが、会場にワンコを連れて入れなかったのでそのまま家路に。。無念

夕焼けの金沢港

最近は陽が落ちるのが遅くなり、少し早めに会社を出れば夕焼けに間に合うようになってきました。

ちょっと雲が気になりますが、金沢港の夕焼けです。
金沢港の夕焼け

そういえば最近釣りしてないな

このままマジックアワーまでタイムラプスで撮ろうかと思っていたのですが、
目の前で釣りを始める人が居たのでちょっと場所を変えて。
マジックアワーの金沢港

そろそろシーバスでも釣りに行こかな。

カテゴリーでインスタンス変数的なものを追加する

objective-cのカテゴリーって便利ですね。

最近はついついなんでもカテゴリーで実装しちゃいますが(ダメ!)、この機能には弱点があります。
それは、インスタンス変数が追加できないことです。
(C#の拡張メソッドもそうですよね。)

まぁ継承して使えばなにも問題は無いのですが、継承するほどの事でもないな、って時はこんな方法で解決できます。

ソースを見たらすぐに理解できると思います。

#import <objc/runtime.h>

//宣言
@interface UIView (Title)

@property (nonatomic) NSString* title;

@end

//実装
@implementation UIView (Title)

static NSString* title_key = @"title-key";

-(void)setTitle:(NSString*) title
{
    objc_setAssociatedObject(self, title_key, title, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-(NSString*) title
{
    return objc_getAssociatedObject(self, title_key);
}

@end


objc_setAssociatedObject、objc_getAssociatedObjectを使うと保存出来る事が分かるのではないでしょうか。

ちょっと補足すると、これは関連参照という機能を使っており、オブジェクトとオブジェクトとを特定のキーで紐付けすることで保持する機能です。
この例ではインスタンス変数なのでselfに対してtitleを関連付けて保存しているのです。

また、上記ソースを見て、「プロパティの破棄してないやんけ!(激怒)」と思いますよね。
これも関連参照の便利な所なのですが、対象のオブジェクトが破棄された時に自動的に関連付けたオブジェクトも破棄してくれます。カシコイですね。

Objective-cでBASE64変換

Amazon Web Serviceを使うのにBase64が必要となったんですけど、cocoaには含まれてないんですね。

今までライブラリに含まれている環境ばかりで育ったゆとりおじさんなもんで、ちょっとショックだったんですけど、
wikiで調べてみるとそんなに難しい変換ではなさそう。

Base64変換の手順を以下に挙げる。
  元データを6bitずつに分割。(6bitに満たない分は0を追加して6bitにする)
  各6bitの値を変換表を使って4文字ずつ変換。(4文字に満たない分は = 記号を追加して4文字にする)

変換例
1. 元データ
   文字列: “ABCDEFG”
   16進表現: 41, 42, 43, 44, 45, 46, 47
   2進表現: 0100 0001, 0100 0010, 0100 0011, 0100 0100, 0100 0101, 0100 0110, 0100 0111
2. 6bitずつに分割
   010000 010100 001001 000011 010001 000100 010101 000110 010001 11
3. 2bit余るので、4bit分0を追加して6bitにする
   010000 010100 001001 000011 010001 000100 010101 000110 010001 110000
4. 変換表により、4文字ずつ変換
   “QUJD”,”REVG”,”Rw”
5. 2文字余るので、2文字分 = 記号を追加して4文字にする
   “QUJD”,”REVG”,”Rw==”
6. Base64文字列
   “QUJDREVGRw==”
   http://ja.wikipedia.org/wiki/Base64

元データを6bitずつに分割して、そこから得られた値を変換表を使って対応する文字を取得すればいいんですな。
4文字ずつ変換ってことは6×4=24bit ・・3バイトずつ処理すれば良いってことで、
図解するとこんな感じ?
 ■■■■■■■■
 ■■■■
■■■■
 ■■
■■■■■■
 :1文字目 :2文字目 :3文字目 :4文字目

せっかくなのでNSDataのカテゴリにしてみました。

インターフェース部


@interface NSData (Base64)

//BASE64でエンコードされた文字列をデコードする
+(NSData*)dataWithBase64String:(NSString*)string;

//レシーバーの内容をBASE64でエンコードします
-(NSString*)stringByBase64Encode;

@end

実装側はこんな感じ



@implementation NSData (Base64)

//変換用テーブル
const static char base64table[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

/**
 * BASE64でエンコードされた文字列をデコードする
 */
+(NSData*)dataWithBase64String:(NSString*)string
{
    if (!string || string.length == 0) return nil;
    
    unsigned char idx[4];
    unsigned char val1,val2,val3;
    char buf;

    unsigned long datalength = string.length;
    NSMutableData* result = [NSMutableData data];
    
    //ポインタで処理するためにchar*へ変換
    const char* src = [string cStringUsingEncoding:NSASCIIStringEncoding];
    
    for(int i=0;i<datalength;i += 4)
    {
        //base64の先頭からのインデックスを求める(4文字ずつ処理する)
        for (int j=0; j < 4; j++)
        {
            buf = *(src + i + j);
            idx[j] = (buf == '=') ? 0 : strchr(base64table, buf) - base64table;
        }
        
        //求めたインデックスからバイト値を求める(6bit×4 から 8bit×3を生成)
        val1 = ((idx[0] & 0x3F) << 2 | (idx[1] & 0x30) >> 4 );
        val2 = ((idx[1] & 0x0F) << 4 | (idx[2] & 0x3C) >> 2 );
        val3 = ((idx[2] & 0x03) << 6 | (idx[3] & 0x3F) >> 0 );
        
        //結果を保存
        [result appendBytes:&val1 length:1];
        [result appendBytes:&val2 length:1];
        [result appendBytes:&val3 length:1];
    }
    
    return result;
}


/**
 * レシーバーの内容をBASE64でエンコードします
 */
-(NSString*) stringByBase64Encode
{
    unsigned char buf1,buf2,buf3;
    unsigned char idx[4];
    char bufchar[2];
    bufchar[1] = '\0';
    
    NSMutableString *result = [NSMutableString string];
    
    unsigned long datalength = self.length;
    const unsigned char* data = [self bytes];

    for(int i = 0 ; i < datalength ; i += 3 )
    {
        //2バイト目、3バイト目が存在するかチェック
        bool flg2 = ((i+1) < datalength);
        bool flg3 = ((i+2) < datalength);
        
        
        //3バイト(24bit)ずつ処理
        buf1 = data[i];
        buf2 = flg2 ? data[i+1]: '\0';
        buf3 = flg3 ? data[i+2]: '\0';
        
        //24bitを6bitに分け、それぞれに対応する文字へのインデックスを求める
        idx[0] = (buf1  >> 2);                        //buf1の6bit
        idx[1] = (buf1 & 0x03) << 4 | (buf2 >> 4);    //buf1の2bitとbuf2の4bit
        idx[2] = (buf2 & 0x0F) << 2 | (buf3 >> 6);    //buf2の4bitとbuf3の2bit
        idx[3] = (buf3 & 0x3F);                       //buf3の6bit
        
        //結果へ格納
        for (int j=0; j<4; j++)
        {
            if ((j == 2 && !flg2) || (j == 3 && !flg3))
            {
                [result appendString:@"="];                
            }
            else
            {
                bufchar[0] = base64table[idx[j]];
                [result appendString:[NSString stringWithCString:bufchar encoding:NSASCIIStringEncoding]];
            }
        }        
    }
    
    return result;
}

@end

実行速度は計っていないですが、デコード側でテーブルの検索を行っているので結構遅いのではないかと思います。
とはいえ、自分が使う分には十分に動作しているのでまぁいいや。