Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
folium-app
GitHub Repository: folium-app/Folium
Path: blob/a-new-beginning/Folium-iOS/Structures/Bruno.swift
2 views
//
//  Bruno.swift
//  Bruno
//
//  Created by Emil Wojtaszek on 03/04/2018.
//  Copyright © 2018 AppUnite.com. All rights reserved.
//

import Accelerate
import UIKit

protocol Bufferable {
    var buffer: vImage_Buffer { get }
    var height: Int { get }
    var width: Int { get }
    var rowBytes: Int { get }
}

class RawBuffer: Bufferable {
    private(set) var bytes: Data
    
    let width: Int
    let height: Int
    let rowBytes: Int

    init(data: Data, width: Int, height: Int, rowBytes: Int) {
        self.bytes = data
        self.width = width
        self.height = height
        self.rowBytes = rowBytes
    }

    var buffer: vImage_Buffer {
        let ptr = bytes.withUnsafeBytes { (rawBufferPointer: UnsafeRawBufferPointer) -> UnsafeMutablePointer<UInt8>? in
            guard let baseAddress = rawBufferPointer.baseAddress else {
                return nil
            }
            
            return UnsafeMutablePointer<UInt8>(mutating: baseAddress.assumingMemoryBound(to: UInt8.self))
        }

        return vImage_Buffer(data: ptr, height: vImagePixelCount(height), width: vImagePixelCount(width), rowBytes: rowBytes)
    }
}

struct Bruno {
    static func decode(srcBuffer: Bufferable) -> Bufferable? {
        let width = srcBuffer.width
        let height = srcBuffer.height

        let rowBytes = width * 3
        let dstData = Data(count: height * rowBytes)
        let dstBuffer = RawBuffer(data: dstData, width: width, height: height, rowBytes: rowBytes)

        var buffer565 = srcBuffer.buffer
        var buffer888 = dstBuffer.buffer

        let error = vImageConvert_RGB565toRGB888(&buffer565, &buffer888, vImage_Flags(kvImageNoFlags))
        guard error == kvImageNoError else {
            return nil
        }

        return dstBuffer
    }

    static func encode(srcBuffer: Bufferable) -> Bufferable? {
        let width = srcBuffer.width
        let height = srcBuffer.height

        let rowBytes = width * 2
        let dstData = Data(count: height * rowBytes)
        let dstBuffer = RawBuffer(data: dstData, width: width, height: height, rowBytes: rowBytes)

        var buffer565 = dstBuffer.buffer
        var buffer8888 = srcBuffer.buffer

        let error = vImageConvert_RGBA8888toRGB565(&buffer8888, &buffer565, vImage_Flags(kvImageNoFlags))
        guard error == kvImageNoError else {
            return nil
        }
        
        return dstBuffer
    }
}

extension UIImage {
    func encodeRGB565(width: Int, height: Int) -> Data? {
        guard let srcBytes = self.buffer(width: width, height: height)
            else { return nil }

        guard let dstBuffer = Bruno.encode(srcBuffer: srcBytes)
            else { return nil }

        return Data(bytes: dstBuffer.buffer.data, count: dstBuffer.buffer.rowBytes * Int(dstBuffer.buffer.height))
    }
}

extension Data {
    func decodeRGB565(width: Int, height: Int) -> UIImage? {
        let srcBuffer = RawBuffer(data: self, width: width, height: height, rowBytes: width * 2)

        guard let dstBuffer = Bruno.decode(srcBuffer: srcBuffer)
            else { return nil }

        var format = vImage_CGImageFormat(bitsPerComponent: 8, bitsPerPixel: 24, colorSpace: nil, bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue), version: 0, decode: nil, renderingIntent: .defaultIntent)

        var buffer888 = dstBuffer.buffer
        var error: vImage_Error = 0
        let vImage = vImageCreateCGImageFromBuffer(&buffer888, &format, nil, nil, vImage_Flags(kvImageNoFlags), &error)
        guard error == kvImageNoError else { return nil }

        return vImage.flatMap {
            UIImage(cgImage: $0.takeRetainedValue(), scale: 1.0, orientation: .up)
        }
    }
}

internal extension UIImage {
    func buffer(width: Int, height: Int) -> RawBuffer? {
        guard let cgImage = self.cgImage
            else { return nil }

        let rowBytes = 4 * width
        var bytes = Array<UInt8>.init(repeating: 0, count: rowBytes * height)

        guard let context = CGContext(data: &bytes, width: width, height: height, bitsPerComponent: 8, bytesPerRow: rowBytes, space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue)
            else { return nil }

        context.interpolationQuality = .high
        context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))

        let data = bytes.withUnsafeBufferPointer { bufferPointer -> Data in
            return Data(buffer: bufferPointer)
        }
        
        return RawBuffer(data: data, width: width, height: height, rowBytes: rowBytes)
    }
}