// // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import XCTest @testable import Signal /// Do bitmap images correctly merge adjacent pixels? class BitmapsImagePixelMergingTest: XCTestCase { func testMergeAdjacentPixels() { for testCase in TestCase.all { XCTAssertEqual( testCase.image.gridDrawingByMergingAdjacentPixels( deadzone: testCase.deadzone ), testCase.gridDrawing ) } } } private struct TestCase { let image: Bitmaps.Image let deadzone: Bitmaps.Rect let gridDrawing: Bitmaps.GridDrawing static let all: [TestCase] = [.one, .two, .three, .four] /// 3x4, all filled in, no dead zone. static let one: TestCase = TestCase( image: Bitmaps.Image(width: 3, height: 4, bytes: [Byte](repeating: .xxx, count: 12)), deadzone: Bitmaps.Rect(x: 0, y: 0, width: 0, height: 0), gridDrawing: Bitmaps.GridDrawing( width: 3, height: 4, segments: [ .horizontal(x: 0, y: 0, length: 3), .horizontal(x: 0, y: 1, length: 3), .horizontal(x: 0, y: 2, length: 3), .horizontal(x: 0, y: 3, length: 3), .vertical(x: 0, y: 0, length: 4), .vertical(x: 1, y: 0, length: 4), .vertical(x: 2, y: 0, length: 4) ]) ) /// 6x6, mostly filled in, with a central deadzone. static let two: TestCase = TestCase( image: Bitmaps.Image(width: 6, height: 6, bytes: [ .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .ooo, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .ooo, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .ooo ]), deadzone: Bitmaps.Rect(x: 1, y: 1, width: 4, height: 4), gridDrawing: Bitmaps.GridDrawing(width: 6, height: 6, segments: [ .horizontal(x: 0, y: 0, length: 6), .horizontal(x: 0, y: 1, length: 4), .horizontal(x: 5, y: 1, length: 1), .horizontal(x: 0, y: 2, length: 2), .horizontal(x: 4, y: 2, length: 2), .horizontal(x: 0, y: 3, length: 2), .horizontal(x: 4, y: 3, length: 2), .horizontal(x: 0, y: 4, length: 6), .horizontal(x: 0, y: 5, length: 5), .vertical(x: 0, y: 0, length: 6), .vertical(x: 1, y: 0, length: 6), .vertical(x: 2, y: 0, length: 2), .vertical(x: 2, y: 4, length: 2), .vertical(x: 3, y: 0, length: 2), .vertical(x: 3, y: 4, length: 2), .vertical(x: 4, y: 0, length: 1), .vertical(x: 4, y: 2, length: 4), .vertical(x: 5, y: 0, length: 5) ]) ) /// 7x7, fully filled in, with a small central deadzone. /// /// Desired result: /// /// .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, /// .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, /// .xxx, .xxx, .xxx, .ooo, .xxx, .xxx, .xxx, /// .xxx, .xxx, .ooo, .ooo, .ooo, .xxx, .xxx, /// .xxx, .xxx, .xxx, .ooo, .xxx, .xxx, .xxx, /// .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, /// .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx /// static let three: TestCase = TestCase( image: Bitmaps.Image(width: 7, height: 7, bytes: [ .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx ]), deadzone: Bitmaps.Rect(x: 2, y: 2, width: 3, height: 3), gridDrawing: Bitmaps.GridDrawing(width: 7, height: 7, segments: [ .horizontal(x: 0, y: 0, length: 7), .horizontal(x: 0, y: 1, length: 7), .horizontal(x: 0, y: 2, length: 3), .horizontal(x: 4, y: 2, length: 3), .horizontal(x: 0, y: 3, length: 2), .horizontal(x: 5, y: 3, length: 2), .horizontal(x: 0, y: 4, length: 3), .horizontal(x: 4, y: 4, length: 3), .horizontal(x: 0, y: 5, length: 7), .horizontal(x: 0, y: 6, length: 7), .vertical(x: 0, y: 0, length: 7), .vertical(x: 1, y: 0, length: 7), .vertical(x: 2, y: 0, length: 3), .vertical(x: 2, y: 4, length: 3), .vertical(x: 3, y: 0, length: 2), .vertical(x: 3, y: 5, length: 2), .vertical(x: 4, y: 0, length: 3), .vertical(x: 4, y: 4, length: 3), .vertical(x: 5, y: 0, length: 7), .vertical(x: 6, y: 0, length: 7) ]) ) /// 7x7, fully filled in, with a large central deadzone. /// /// Desired result: /// /// .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, /// .xxx, .xxx, .xxx, .ooo, .xxx, .xxx, .xxx, /// .xxx, .xxx, .ooo, .ooo, .ooo, .xxx, .xxx, /// .xxx, .ooo, .ooo, .ooo, .ooo, .ooo, .xxx, /// .xxx, .xxx, .ooo, .ooo, .ooo, .xxx, .xxx, /// .xxx, .xxx, .xxx, .ooo, .xxx, .xxx, .xxx, /// .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx /// static let four: TestCase = TestCase( image: Bitmaps.Image(width: 7, height: 7, bytes: [ .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx, .xxx ]), deadzone: Bitmaps.Rect(x: 1, y: 1, width: 5, height: 5), gridDrawing: Bitmaps.GridDrawing(width: 7, height: 7, segments: [ .horizontal(x: 0, y: 0, length: 7), .horizontal(x: 0, y: 1, length: 3), .horizontal(x: 4, y: 1, length: 3), .horizontal(x: 0, y: 2, length: 2), .horizontal(x: 5, y: 2, length: 2), .horizontal(x: 0, y: 3, length: 1), .horizontal(x: 6, y: 3, length: 1), .horizontal(x: 0, y: 4, length: 2), .horizontal(x: 5, y: 4, length: 2), .horizontal(x: 0, y: 5, length: 3), .horizontal(x: 4, y: 5, length: 3), .horizontal(x: 0, y: 6, length: 7), .vertical(x: 0, y: 0, length: 7), .vertical(x: 1, y: 0, length: 3), .vertical(x: 1, y: 4, length: 3), .vertical(x: 2, y: 0, length: 2), .vertical(x: 2, y: 5, length: 2), .vertical(x: 3, y: 0, length: 1), .vertical(x: 3, y: 6, length: 1), .vertical(x: 4, y: 0, length: 2), .vertical(x: 4, y: 5, length: 2), .vertical(x: 5, y: 0, length: 3), .vertical(x: 5, y: 4, length: 3), .vertical(x: 6, y: 0, length: 7) ]) ) } private extension Bitmaps.GridDrawing.Segment { static func horizontal(x: Int, y: Int, length: Int) -> Bitmaps.GridDrawing.Segment { return Bitmaps.GridDrawing.Segment( dimension: .horizontal, start: Bitmaps.Point(x: x, y: y), length: length ) } static func vertical(x: Int, y: Int, length: Int) -> Bitmaps.GridDrawing.Segment { return Bitmaps.GridDrawing.Segment( dimension: .vertical, start: Bitmaps.Point(x: x, y: y), length: length ) } } /// Binary, but with the same number of characters in "one" and "zero". private enum Byte { case xxx case ooo } private extension Bitmaps.Image { init(width: Int, height: Int, bytes: [Byte]) { self.init(width: width, height: height, rawBytes: bytes.asRawBytes) } } private extension Array where Element == Byte { var asRawBytes: [UInt8] { return flatMap { byte in switch byte { case .xxx: return [UInt8(integerLiteral: 1), 1, 1, 1] case .ooo: return [UInt8(integerLiteral: 0), 0, 0, 0] } } } }