Spaces:
Runtime error
Runtime error
| // Copyright 2019 The TensorFlow Authors. All Rights Reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| // ============================================================================= | |
| import Accelerate | |
| import Foundation | |
| extension CVPixelBuffer { | |
| var size: CGSize { | |
| return CGSize(width: CVPixelBufferGetWidth(self), height: CVPixelBufferGetHeight(self)) | |
| } | |
| /// Returns a new `CVPixelBuffer` created by taking the self area and resizing it to the | |
| /// specified target size. Aspect ratios of source image and destination image are expected to be | |
| /// same. | |
| /// | |
| /// - Parameters: | |
| /// - from: Source area of image to be cropped and resized. | |
| /// - to: Size to scale the image to(i.e. image size used while training the model). | |
| /// - Returns: The cropped and resized image of itself. | |
| func resize(from source: CGRect, to size: CGSize) -> CVPixelBuffer? { | |
| let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: self.size) | |
| guard rect.contains(source) else { | |
| os_log("Resizing Error: source area is out of index", type: .error) | |
| return nil | |
| } | |
| guard rect.size.width / rect.size.height - source.size.width / source.size.height < 1e-5 | |
| else { | |
| os_log( | |
| "Resizing Error: source image ratio and destination image ratio is different", | |
| type: .error) | |
| return nil | |
| } | |
| let inputImageRowBytes = CVPixelBufferGetBytesPerRow(self) | |
| let imageChannels = 4 | |
| CVPixelBufferLockBaseAddress(self, CVPixelBufferLockFlags(rawValue: 0)) | |
| defer { CVPixelBufferUnlockBaseAddress(self, CVPixelBufferLockFlags(rawValue: 0)) } | |
| // Finds the address of the upper leftmost pixel of the source area. | |
| guard | |
| let inputBaseAddress = CVPixelBufferGetBaseAddress(self)?.advanced( | |
| by: Int(source.minY) * inputImageRowBytes + Int(source.minX) * imageChannels) | |
| else { | |
| return nil | |
| } | |
| // Crops given area as vImage Buffer. | |
| var croppedImage = vImage_Buffer( | |
| data: inputBaseAddress, height: UInt(source.height), width: UInt(source.width), | |
| rowBytes: inputImageRowBytes) | |
| let resultRowBytes = Int(size.width) * imageChannels | |
| guard let resultAddress = malloc(Int(size.height) * resultRowBytes) else { | |
| return nil | |
| } | |
| // Allocates a vacant vImage buffer for resized image. | |
| var resizedImage = vImage_Buffer( | |
| data: resultAddress, | |
| height: UInt(size.height), width: UInt(size.width), | |
| rowBytes: resultRowBytes | |
| ) | |
| // Performs the scale operation on cropped image and stores it in result image buffer. | |
| guard vImageScale_ARGB8888(&croppedImage, &resizedImage, nil, vImage_Flags(0)) == kvImageNoError | |
| else { | |
| return nil | |
| } | |
| let releaseCallBack: CVPixelBufferReleaseBytesCallback = { mutablePointer, pointer in | |
| if let pointer = pointer { | |
| free(UnsafeMutableRawPointer(mutating: pointer)) | |
| } | |
| } | |
| var result: CVPixelBuffer? | |
| // Converts the thumbnail vImage buffer to CVPixelBuffer | |
| let conversionStatus = CVPixelBufferCreateWithBytes( | |
| nil, | |
| Int(size.width), Int(size.height), | |
| CVPixelBufferGetPixelFormatType(self), | |
| resultAddress, | |
| resultRowBytes, | |
| releaseCallBack, | |
| nil, | |
| nil, | |
| &result | |
| ) | |
| guard conversionStatus == kCVReturnSuccess else { | |
| free(resultAddress) | |
| return nil | |
| } | |
| return result | |
| } | |
| /// Returns the RGB `Data` representation of the given image buffer. | |
| /// | |
| /// - Parameters: | |
| /// - isModelQuantized: Whether the model is quantized (i.e. fixed point values rather than | |
| /// floating point values). | |
| /// - Returns: The RGB data representation of the image buffer or `nil` if the buffer could not be | |
| /// converted. | |
| func rgbData( | |
| isModelQuantized: Bool | |
| ) -> Data? { | |
| CVPixelBufferLockBaseAddress(self, .readOnly) | |
| defer { CVPixelBufferUnlockBaseAddress(self, .readOnly) } | |
| guard let sourceData = CVPixelBufferGetBaseAddress(self) else { | |
| return nil | |
| } | |
| let width = CVPixelBufferGetWidth(self) | |
| let height = CVPixelBufferGetHeight(self) | |
| let sourceBytesPerRow = CVPixelBufferGetBytesPerRow(self) | |
| let destinationBytesPerRow = Constants.rgbPixelChannels * width | |
| // Assign input image to `sourceBuffer` to convert it. | |
| var sourceBuffer = vImage_Buffer( | |
| data: sourceData, | |
| height: vImagePixelCount(height), | |
| width: vImagePixelCount(width), | |
| rowBytes: sourceBytesPerRow) | |
| // Make `destinationBuffer` and `destinationData` for its data to be assigned. | |
| guard let destinationData = malloc(height * destinationBytesPerRow) else { | |
| os_log("Error: out of memory", type: .error) | |
| return nil | |
| } | |
| defer { free(destinationData) } | |
| var destinationBuffer = vImage_Buffer( | |
| data: destinationData, | |
| height: vImagePixelCount(height), | |
| width: vImagePixelCount(width), | |
| rowBytes: destinationBytesPerRow) | |
| // Convert image type. | |
| switch CVPixelBufferGetPixelFormatType(self) { | |
| case kCVPixelFormatType_32BGRA: | |
| vImageConvert_BGRA8888toRGB888(&sourceBuffer, &destinationBuffer, UInt32(kvImageNoFlags)) | |
| case kCVPixelFormatType_32ARGB: | |
| vImageConvert_BGRA8888toRGB888(&sourceBuffer, &destinationBuffer, UInt32(kvImageNoFlags)) | |
| default: | |
| os_log("The type of this image is not supported.", type: .error) | |
| return nil | |
| } | |
| // Make `Data` with converted image. | |
| let imageByteData = Data( | |
| bytes: destinationBuffer.data, count: destinationBuffer.rowBytes * height) | |
| if isModelQuantized { return imageByteData } | |
| let imageBytes = [UInt8](imageByteData) | |
| return Data(copyingBufferOf: imageBytes.map { Float($0) / Constants.maxRGBValue }) | |
| } | |
| } | |