refactored decode image function

This commit is contained in:
Haik Aslanyan
2020-03-06 13:30:40 +04:00
parent 8536a43049
commit e41f87bb80
2 changed files with 127 additions and 16 deletions
+124 -11
View File
@@ -21,20 +21,120 @@
// SOFTWARE.
import UIKit
import CoreImage
class RCImageDecoder {
final class RCImageDecoder {
private var context = CIContext()
func process(buffer: UnsafeMutableBufferPointer<UInt8>, size: Int) throws -> [RCBit] {
let data = RCMatrix<UInt8>(rows: size, items: buffer)
let points = try scanControlPoints(data)
let imageData = fixPerspective(data, points: points)
let bits = decode(imageData)
return bits
}
func decode(_ image: UIImage) throws -> [RCBit] {
guard let cgImage = image.cgImage else { throw RCCoder.RCCoderError.decoding }
var pixelValues = [UInt8](repeating: 0, count: cgImage.width * cgImage.height)
let size = image.cgImage!.height
var pixelData = UnsafeMutableRawPointer.allocate(byteCount: size * size, alignment: MemoryLayout<UInt8>.alignment)
let context = CGContext(data: pixelData, width: size, height: size, bitsPerComponent: 8, bytesPerRow: size, space: CGColorSpaceCreateDeviceGray(), bitmapInfo: CGImageAlphaInfo.none.rawValue)
context?.draw(image.cgImage!, in: CGRect(origin: .zero, size: CGSize(width: size, height: size)))
let buffer = UnsafeMutableBufferPointer(start: pixelData.assumingMemoryBound(to: UInt8.self), count: size * size)
let bits = try process(buffer: buffer, size: size)
defer {
pixelData.deallocate()
}
return bits
}
}
extension RCImageDecoder {
private func scanControlPoints( _ data: RCMatrix<UInt8>) throws -> [CGPoint] {
var points = [CGPoint]()
let range = (CGFloat(1.8)...CGFloat(2.2))
for rowIndex in 0..<data.rows {
let rowBuffer = data.row(for: rowIndex)
var pixelBuffer = [RCPixelPattern]()
let first = RCPixelPattern.init(bit: (rowBuffer[0] > 180 ? RCBit.zero : RCBit.one), startPoint: CGPoint.init(x: 0, y: CGFloat(rowIndex)), count: 0)
pixelBuffer.append(first)
rowBuffer.enumerated().forEach { value in
let bit = value.element > 180 ? RCBit.zero : RCBit.one
if pixelBuffer.last!.bit == bit {
pixelBuffer[pixelBuffer.count - 1].count += 1
} else {
let pixel = RCPixelPattern.init(bit: bit, startPoint: CGPoint.init(x: CGFloat(value.offset), y: CGFloat(rowIndex)), count: 1)
pixelBuffer.append(pixel)
}
}
guard pixelBuffer.count >= 7 else { break }
for pixelIndex in 2..<(pixelBuffer.count - 4) {
let pixel = pixelBuffer[pixelIndex]
guard pixel.bit == .one, pixel.count >= 10 else { continue }
let leftTwo = pixelBuffer[pixelIndex - 2]
let leftOne = pixelBuffer[pixelIndex - 1]
let rightOne = pixelBuffer[pixelIndex + 1]
let rightTwo = pixelBuffer[pixelIndex + 2]
guard leftTwo.bit == .one else { continue }
guard leftOne.bit == .zero else { continue }
guard rightOne.bit == .zero else { continue }
guard rightTwo.bit == .one else { continue }
guard range.contains(pixel.count / leftTwo.count) else { continue }
guard range.contains(pixel.count / leftOne.count) else { continue }
guard range.contains(pixel.count / rightOne.count) else { continue }
guard range.contains(pixel.count / rightTwo.count) else { continue }
let point = CGPoint.init(x: pixel.startPoint.x, y: pixel.startPoint.y + CGFloat(pixel.count) / 2)
points.append(point)
}
}
return points
}
private func fixPerspective(_ data: RCMatrix<UInt8>, points: [CGPoint]) -> RCMatrix<UInt8> {
let path = UIBezierPath()
path.move(to: points[0])
path.addLine(to: points[1])
path.addLine(to: points[2])
path.addLine(to: points[3])
path.close()
let originalBounds = path.bounds
path.apply(CGAffineTransform(scaleX: sqrt(2.0), y: sqrt(2.0)))
let scaledBounds = path.bounds
path.apply(CGAffineTransform(translationX: (originalBounds.width - scaledBounds.width) / 2 , y: (originalBounds.height - scaledBounds.height) / 2))
let vectors = points.map{CGVector(dx: $0.x, dy: $0.y)}
let image = CIImage() //.image(from: data)
let perspectiveFilter = CIFilter(name: "CIPerspectiveCorrection")!
perspectiveFilter.setValue(image, forKey: "inputImage")
perspectiveFilter.setValue(vectors[0], forKey: "inputTopLeft")
perspectiveFilter.setValue(vectors[1], forKey: "inputTopRight")
perspectiveFilter.setValue(vectors[2], forKey: "inputBottomRight")
perspectiveFilter.setValue(vectors[3], forKey: "inputBottomLeft")
let transform = CGAffineTransform(translationX: image.extent.midX, y: image.extent.midY).rotated(by: CGFloat.pi / 4).translatedBy(x: -image.extent.midX, y: -image.extent.midY)
let correctedImage = perspectiveFilter.outputImage!.transformed(by: transform)
let cgImage = context.createCGImage(correctedImage, from: CGRect(x: 0, y: 0, width: data.columns, height: data.columns))!
var pixelValues = UnsafeMutableRawPointer.allocate(byteCount: cgImage.width * cgImage.height, alignment: MemoryLayout<UInt8>.alignment)
let colorSpace = CGColorSpaceCreateDeviceGray()
let contextRef = CGContext(data: &pixelValues, width: cgImage.width, height: cgImage.height, bitsPerComponent: 8, bytesPerRow: cgImage.width, space: colorSpace, bitmapInfo: 0)
contextRef?.draw(cgImage, in: CGRect(origin: .zero, size: CGSize(width: cgImage.width, height: cgImage.height)))
let imageMatrix = RCMatrix(rows: cgImage.width, items: pixelValues)
let center = CGPoint(x: cgImage.width, y: cgImage.width)
let lineWidth = CGFloat(cgImage.width) * RCConstants.dotSizeScale / 11 * 2 //number of lines including spaces
let mainRadius = (CGFloat(cgImage.width) - lineWidth) / 2
let startAngle = asin(CGFloat(cgImage.width) * RCConstants.dotSizeScale / mainRadius)
data.data.deallocate()
data.data = UnsafeMutableBufferPointer<UInt8>(start: pixelValues.bindMemory(to: UInt8.self, capacity: data.columns * data.rows), count: data.columns * data.rows)
return data
}
private func decode(_ data: RCMatrix<UInt8>) -> [RCBit] {
let size = CGFloat(data.columns)
let lineWidth = size * RCConstants.dotSizeScale / 11 * 2 //number of lines including spaces
let mainRadius = (size - lineWidth) / 2
let startAngle = asin(size * RCConstants.dotSizeScale / mainRadius)
let distancePerBit = (CGFloat.pi / 2 - startAngle * 2) / CGFloat(RCConstants.level1BitesCount)
var points = [CGPoint]()
[RCConstants.level1BitesCount, RCConstants.level2BitesCount, RCConstants.level3BitesCount].enumerated().forEach { row in
@@ -45,13 +145,26 @@ class RCImageDecoder {
let angle = baseDistance + distancePerBit * CGFloat(bitCount) + distancePerBit / 2
let x = cos(angle) * radius
let y = sin(angle) * radius
let point = CGPoint(x: x + center.x.rounded() / 2, y: y + center.y.rounded() / 2)
let point = CGPoint(x: x + size / 2, y: y + size / 2)
points.append(point)
}
}
}
return points.map { imageMatrix[Int($0.x), Int($0.y)] > 200 ? RCBit.zero : RCBit.one }
return points.map { data[Int($0.x), Int($0.y)] > 200 ? RCBit.zero : RCBit.one }
}
}
private extension RCImageDecoder {
struct RCPixelPattern: CustomStringConvertible {
let bit: RCBit
let startPoint: CGPoint
var count: CGFloat
var description: String {
"\(bit.debugDescription), start: \(startPoint.x), count: \(count), \n "
}
}
}
+3 -5
View File
@@ -43,11 +43,9 @@ public extension RCCoder {
func decode(_ image: UIImage) throws -> String {
guard image.size.width == image.size.height else { throw RCError.wrongImageSize }
//make grayscale
//make CVPixelBuffer
//decode image
//decode bits
return "message"
let bits = try imageDecoder.decode(image)
let message = try bitCoder.decode(bits)
return message
}
func validate(_ text: String) -> Bool {