refactored decode image function
This commit is contained in:
@@ -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 "
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user