4. loop 関係のリファクタリングと画像処理関数の作成

reset の実装 (PixelIterator)

当初プログラムを作成している時には、MonoMatrix が主役の予定でいたが、いろいろと作っている内に PixelIterator の方が主役になってきた。画像を読み込んできたりしたときには MonoMatrix として読み込むが、基本的な処理は PixelIterator で作成していく。

昨日の段階で setPos() を何度か呼んでいたので、専用の reset() メソッドを作成した。せっかくなので method chain ができるように自分自身を返すようにした。

    public func reset() -> PixelIterator {
        setPos()
        return self
    }

loop 系の見直しについて

昨日の段階で作成した loop は IteratorSet を closure に渡していた。元々は IteratorSet 内で MonoMatrix から pixelIterator を作成するつもりでいたので、内部変数にアクセスする必要があると考えていたためである。先ほど記述したように pixelIterator をメインに使うようになると、IteratorSet は表にでる必要がないことに気がついた。このため、これまでのメソッドをすべて WithIs というメソッドに変えて、IteratorSet を渡さないものをこれまでのメソッド名にする。

testLoop (ImageLibraryTests)

loop のテストは以下のようになった。loopWithIs 版と同じテストを loop 版でも実行している。先ほど書いた reset() のおかげで loopWithIs 版も1行短くなっている。IteratorSet が使えないので、loop 版は零値のテストのみを実施している。パラメータが渡されないので、closure 外の self から pit1! をキャプチャしている。この時 self は強参照になってしまい、ARC がうまく働かずにメモリリークしてしまうので [ unowned self ] として ARC に所有しないことを宣言する必要がある。

    func testLoop() {
        IteratorSet(pits: [ pit1! ]).loopWithIs { ( set: IteratorSet ) -> () in
            set.setDoubleValue(0.0, at: 0)
        }
        XCTAssertEqual(mmat1!.doubleBuffer, [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ])
        //
        IteratorSet(pits: [ pit1!.reset() ]).loopWithIs {
            $0.setDoubleValue(Double($0.order), at: 0)
        }
        XCTAssertEqual(mmat1!.doubleBuffer, [ 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 ])
        // without IteratorSet
        IteratorSet(pits: [ pit1!.reset() ]).loop { [ unowned self ] in self.pit1!.doubleValue = 0 }
        XCTAssertEqual(mmat1!.doubleBuffer, [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ])
    }

loopWithIs と loop の実装 (PixelIterator)

loopWithIs は前回のメソッド名を変更しただけである。loop の方は closure に IteratorSet を渡さなくしただけである。

    public func loopWithIs(closure: (IteratorSet) -> ()) {
        while !finished {
            closure(self)
            nextPos()
        }
    }
    
    public func loop(closure: () -> ()) {
        while !finished {
            closure()
            nextPos()
        }
    }

testLoopAndSet (ImageLibraryTests)

loopWithIsAndSet のテストは reset() が内部に入っただけで同じである。loopAndSet の方は同様に零値のセットのみしかしていないが、圧倒的に簡単に記述できているのがわかる。

    func testLoopAndSet() {
        IteratorSet(pits: [ pit1! ]).loopWithIsAndSet { set in 0.0 }
        XCTAssertEqual(mmat1!.doubleBuffer, [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ])
        IteratorSet(pits: [ pit1!.reset() ]).loopWithIsAndSet { Double($0.order) }
        XCTAssertEqual(mmat1!.doubleBuffer, [ 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 ])
        // without IteratorSet
        IteratorSet(pits: [ pit1!.reset() ]).loopAndSet { 0.0 }
    }

loopWithIsAndSet と loopAndSet の実装 (PixelIterator)

こちらも loopWithIsAndSet は名前を変えただけ、loopAndSet は IteratorSet を渡さなくなっただけである。

    public func loopWithIsAndSet(closure: (IteratorSet) -> (Double)) {
        while !finished {
            setDoubleValue(closure(self), at: 0)
            nextPos()
        }
    }
    
    public func loopAndSet(closure: () -> (Double)) {
        while !finished {
            setDoubleValue(closure(), at: 0)
            nextPos()
        }
    }

testReduce (ImageLibraryTests)

reduceWithIs のテストも reset() が内部に入っただけで同じである。loopAndSet の方は closure には更新すべき値だけが渡されるので、[ unowned self ] を使って値の更新を行う。

    func testReduce() {
        let product = IteratorSet(pits: [ pit1! ]).reduceWithIs(value: 1.0) { $0 * $1.doubleValue(0)! }
        XCTAssertEqual(product, 540.0)
        let sum = IteratorSet(pits: [ pit1!.reset() ]).reduceWithIs { $0 + $1.doubleValue(0)! }
        XCTAssertEqual(sum, 23.0)
        // without IteratorSet
        let product2 = IteratorSet(pits: [ pit1!.reset() ]).reduce(value: 1.0) { [unowned self] in $0 * self.pit1!.doubleValue }
        XCTAssertEqual(product, 540.0)
        let sum2 = IteratorSet(pits: [ pit1!.reset() ]).reduce { [unowned self] in $0 * self.pit1!.doubleValue }
    }

reduceWithIs, reduce の実装 (PixelIterator)

こちらも reduceWithIs は名前を変えただけ、reduce は IteratorSet を渡さなくなっただけである。closure は更新すべき値だけを渡すようになった。

    public func reduceWithIs(value : Double = 0.0, closure: (Double, IteratorSet) -> (Double)) -> Double {
        var ans : Double = value
        while !finished {
            ans = closure(ans, self)
            nextPos()
        }
        return ans
    }

    public func reduce(value : Double = 0.0, closure: (Double) -> (Double)) -> Double {
        var ans : Double = value
        while !finished {
            ans = closure(ans)
            nextPos()
        }
        return ans
    }

testCheckSameSizePI (ImageLibraryTests)

以前、ImageMatrix プロトコルを受け付ける MonoMatrix と ColorMatrix に対して、大きさを比較する checkSameSize を実装していた。PixelIterator が主役になったのでこちらにも checkSameSize を実装する。テストは MonoMatrix のものを PixelIterator に差し替えただけである。

    func testCheckSameSizePI() {
        var mmat2 = MonoMatrix(rnum: 2, cnum: 3)
        var pcorrect1 = mmat2.pixelIterator()
        XCTAssertTrue(checkSameSize(pit1!, pcorrect1))
        var mmat3 = MonoMatrix(rnum: 8, cnum: 8)
        var pcorrect2 = mmat3.pixelIterator(wrnum: 2, wcnum: 3, offsetr: 4, offsetc: 5)
        XCTAssertTrue(checkSameSize(pit1!, pcorrect1, pcorrect2))
        var pdiffer = mmat3.pixelIterator()
        XCTAssertFalse(checkSameSize(pit1!, pdiffer))
        pdiffer = mmat3.pixelIterator(wrnum: 1, wcnum: 3, offsetr: 3, offsetc: 4)
        XCTAssertFalse(checkSameSize(pit1!, pdiffer))
        pdiffer = mmat2.pixelIterator(wrnum: 1, wcnum: 2)
        XCTAssertFalse(checkSameSize(pit1!, pdiffer))
        XCTAssertFalse(checkSameSize(pit1!, pcorrect1, pcorrect2, pdiffer))
    }

checkSameSizePI の実装 (PixelIterator)

実装は PixelIterator.swift に記述するが、MonoMatrix 版と同様グローバルスコープでの関数とした。Generics は PixelIterator 型で絞り込んでいるので、ImageMatrix Protocol のものとはかち合わない。内容は rnum → wrnum、cnum → wcnum になっているだけで同じである。

public func checkSameSize<T:PixelIterator>(pits: T ...) -> Bool {
    let r = pits[0].wrnum
    let c = pits[0].wcnum
    for p in pits {
        if r != p.wrnum || c != p.wcnum {
            return false
        }
    }
    return true
}

testEqual (ImageLibraryTests)

テストを書きやすくするために == で PixelIterator を比較できるようにしたい。こうなれば、XCTAssertEqual や XCTAssertNotEqual で直接比較が可能になる。この場合のテストは以下のようになる。

    func testEqual() {
        var mmat2 = MonoMatrix(doubleBuffer: [ 3.0, 1.0, 4.0, 1.0, 5.0, 9.0], rnum: 2, cnum: 3)
        var pit2 = mmat2.pixelIterator()
        XCTAssertEqual(pit1!, pit2)
        XCTAssertNotEqual(pit1!, mmat2.pixelIterator(wrnum: 2, wcnum: 2))
        pit2.reset().doubleValue = 4.0
        XCTAssertNotEqual(pit1!, pit2)
        pit2.reset().doubleValue = 3.0
        XCTAssertEqual(pit1!, pit2)
    }

Equatable プロトコルと == の実装 (PixelIterator)

== が使えるようにするには、まずクラスが Equatable プロトコルを受けなければならない。このためにはクラスの先頭を以下のように書き換える。

public class PixelIterator : Equatable {

== は先ほど修正した loop を使えば簡単にかける。 演算子の追加なのでPixelIterator.swift のグローバルスコープに記述する

public func == (lhs : PixelIterator, rhs: PixelIterator) -> Bool {
    var ans : Bool = false
    if checkSameSize(lhs, rhs) {
        ans = true
        IteratorSet(pits: [ lhs.reset(), rhs.reset() ]).loop { [unowned lhs, rhs ] in
            if ans && abs(lhs.doubleValue - rhs.doubleValue) > 1e-9 {
                ans = false
            }
        }
    }
    return ans
}

Equatable プロトコルと == の実装 (MonoMatrix)

ついでに MonoMatrix にも == を追加する。まず Equatable プロトコルを受け付ける。

public class MonoMatrix : ImageMatrix, Equatable {

実装は PixelIterator を作成してしまって、それを比較すればよい。これも MonoMatrix.swift のグローバルスコープに記述する。

public func == (lhs: MonoMatrix, rhs: MonoMatrix) -> Bool {
    return lhs.pixelIterator() == rhs.pixelIterator()
}

簡単な画像処理のテスト (ImageLibraryTests)

実際に loopAndSet を使って fill, addConst, mulconst, addSelf, subSelf, mulSelf, divSelf などを作ってみる。テストはまとめて記載する。

    func testFill() {
        pit1!.fill(255.0)
        XCTAssertEqual(mmat1!, MonoMatrix(doubleBuffer: [Double](count: 6, repeatedValue: 255.0), rnum: 2, cnum: 3))
        pit1!.reset().fill(0.0)
        XCTAssertEqual(mmat1!, MonoMatrix(rnum: 2, cnum: 3))
    }
    
    func testAddConstMulConst() {
        pit1!.addConst(5.0)
        XCTAssertEqual(mmat1!, MonoMatrix(doubleBuffer: [ 8.0, 6.0, 9.0, 6.0, 10.0, 14.0 ], rnum: 2, cnum: 3))
        pit1!.reset().mulConst(2.5)
        XCTAssertEqual(mmat1!, MonoMatrix(doubleBuffer: [ 20.0, 15.0, 22.5, 15.0, 25.0, 35.0 ], rnum: 2, cnum: 3))
    }
    
    func testAddSubMulDivSelf() {
        var mmat2 = MonoMatrix(doubleBuffer: [ 2.0, 9.0, 4.0, 7.0, 5.0, 3.0 ], rnum: 2, cnum: 3)
        var pit2 = mmat2.pixelIterator()
        pit1!.addSelf(pit2)
        XCTAssertEqual(mmat1!, MonoMatrix(doubleBuffer: [ 5.0, 10.0, 8.0, 8.0, 10.0, 12.0 ], rnum: 2, cnum: 3))
        pit1!.subSelf(pit2)
        XCTAssertEqual(mmat1!, MonoMatrix(doubleBuffer: [ 3.0, 1.0, 4.0, 1.0, 5.0, 9.0 ], rnum: 2, cnum: 3))
        pit1!.mulSelf(pit2)
        XCTAssertEqual(mmat1!, MonoMatrix(doubleBuffer: [ 6.0, 9.0, 16.0, 7.0, 25.0, 27.0 ], rnum: 2, cnum: 3))
        pit1!.divSelf(pit2)
        XCTAssertEqual(mmat1!, MonoMatrix(doubleBuffer: [ 3.0, 1.0, 4.0, 1.0, 5.0, 9.0 ], rnum: 2, cnum: 3))
    }

上記メソッドの実装 (PixelIterator)

基本的にピクセルごとの計算なので loopAndSet であっという間にかけてしまった。これらのメソッドは method chain されることも期待して self を返すようにしている。このとき呼び出し側でいちいち reset() するのは面倒なので、return するときに reset() するように決めた。引数で渡されるものがある場合にはそちらも reset() した。

    public func fill(value: Double) -> PixelIterator {
        IteratorSet(pits: [ self ]).loopAndSet { value }
        return self.reset()
    }

    public func addConst(value: Double) -> PixelIterator {
        IteratorSet(pits: [ self ]).loopAndSet { [ unowned self ] in self.doubleValue + value }
        return self.reset()
    }
    
    public func mulConst(value: Double) -> PixelIterator {
        IteratorSet(pits: [ self ]).loopAndSet { [ unowned self ] in self.doubleValue * value }
        return self.reset()
    }
    
    public func addSelf(other: PixelIterator) -> PixelIterator {
        IteratorSet(pits: [ self, other ]).loopAndSet { [ unowned self, other ] in self.doubleValue + other.doubleValue }
        other.reset()
        return self.reset()
    }
    
    public func subSelf(other: PixelIterator) -> PixelIterator {
        IteratorSet(pits: [ self, other ]).loopAndSet { [ unowned self, other ] in self.doubleValue - other.doubleValue }
        other.reset()
        return self.reset()
    }

    public func mulSelf(other: PixelIterator) -> PixelIterator {
        IteratorSet(pits: [ self, other ]).loopAndSet { [ unowned self, other ] in self.doubleValue * other.doubleValue }
        other.reset()
        return self.reset()
    }

    public func divSelf(other: PixelIterator) -> PixelIterator {
        IteratorSet(pits: [ self, other ]).loopAndSet { [ unowned self, other ] in self.doubleValue / other.doubleValue }
        other.reset()
        return self.reset()
    }

次はもう少し複雑なものを書いてみる予定。