Таким образом, проблема получает UIImage, который затем обрезается с помощью класса ImageCrop. Затем, когда я читаю это обрезанное изображение в классе ImagePixelHelper, оно перестает работать правильно, если обрезанное изображение имеет другое количество байтов, чем ожидаемое. Например, после обрезки UIImage мы вычисляем ожидаемую длину массива данных изображения, выполнив:
- image.size.width * image.size.height * 3 для RGB
- image.size.width * image.size.height * 4 для RGBA
Затем с помощью CFDataGetBytePointer (image.cgImage! .dataProvider! .data) и проверки длины этого массива мы находим, чторазмеры очень разные. Это означает, что мы не знаем, в каком формате находится изображение, поэтому мы не можем получить цвет пикселя, потому что это неизвестный формат.
Примечания:
- Класс ImageCrop имеет дело с обрезкой данного UIImage, с данным классом CGRect
- ImagePixelHelper имеет дело с предоставлением цвета пикселя при заданной координате X, Y
Оба класса протестированы и работают как ожидается автономно, но ломаются при совместной работе
/// Helper class for cropping images
class ImageCrop
{
/// Function crops the image to the given rect and returns a new image
///
/// - Parameter rect: The rect to crop to
/// - Returns: A new image which is cropped to the rect passed in
static func crop(image:UIImage, rect:CGRect) -> UIImage
{
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
let drawRect = CGRect(x: -rect.origin.x, y: -rect.origin.y,
width: image.size.width, height: image.size.height)
context?.clip(to: CGRect(x: 0, y: 0,
width: rect.size.width, height: rect.size.height + 1))
image.draw(in: drawRect)
let subImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return subImage!
}
}
-
/// Helper class finds pixels values within the given image
class ImagePixelHelper
{
/// The image to find pixels in
private let image: UIImage
/// The image data
private var data:UnsafePointer<UInt8>?
/// The expected length of the image data
private var expectedLengthA:Int?
/// The expected rgb length of the image
private var expectedLengthRGB:Int?
/// The expected rgba length of the image
private var expectedLengthRGBA:Int?
/// The actual number of bytes in the image
private var numBytes: CFIndex?
private var pixelData: CFData?
/// Default constructor for the ImagePixelHelper
/// - Parameter image: The image to find pixels in
init(image: UIImage)
{
self.image = image
}
/// Function loads all the image data for quick access later
func loadImageData() throws
{
// Get the image as a CGImage
let cgImage : CGImage = image.cgImage!
pixelData = cgImage.dataProvider?.data
// Get the pointer to the start of the array
data = CFDataGetBytePtr(self.pixelData)!
// Calculate the expected lengths
expectedLengthA = Int(image.size.width * image.size.height)
expectedLengthRGB = 3 * expectedLengthA!
expectedLengthRGBA = 4 * expectedLengthA!
// Get the length of the data
numBytes = CFDataGetLength(pixelData)
}
/// Function sets all member vars to nil to help speed up GC
func unloadImageData()
{
data = nil
expectedLengthA = nil
expectedLengthRGB = nil
expectedLengthRGBA = nil
numBytes = nil
}
/// Function gets the pixel colour from the given image using the provided x y coordinates
/// - Parameter pixelX: The X Pixel coordinate
/// - Parameter pixelY: The Y Pixel coordinate
/// - Parameter bgr: Whether we should return BGR, by default this is true so must be set if you want RGB
func getPixelValueFromImage(pixelX: Int, pixelY:Int, bgr: Bool = true) -> UIColor
{
// If we have all the required member vars for this operation
if let data = self.data,
let expectedLengthA = self.expectedLengthA,
let expectedLengthRGB = self.expectedLengthRGB,
let expectedLengthRGBA = self.expectedLengthRGBA,
let numBytes = self.numBytes
{
// Get the index of the pixel we want
let index = Int(image.size.width) * pixelY + pixelX
// Check the number of bytes
switch numBytes
{
case expectedLengthA:
return UIColor(red: 0, green: 0, blue: 0, alpha: CGFloat(data[index])/255.0)
case expectedLengthRGB:
if bgr
{
return UIColor(red: CGFloat(data[3*index+2])/255.0, green: CGFloat(data[3*index+1])/255.0, blue: CGFloat(data[3*index])/255.0, alpha: 1.0)
}
else
{
return UIColor(red: CGFloat(data[3*index])/255.0, green: CGFloat(data[3*index+1])/255.0, blue: CGFloat(data[3*index+2])/255.0, alpha: 1.0)
}
case expectedLengthRGBA:
if bgr
{
return UIColor(red: CGFloat(data[4*index+2])/255.0, green: CGFloat(data[4*index+1])/255.0, blue: CGFloat(data[4*index])/255.0, alpha: CGFloat(data[4*index+3])/255.0)
}
else
{
return UIColor(red: CGFloat(data[4*index])/255.0, green: CGFloat(data[4*index+1])/255.0, blue: CGFloat(data[4*index+2])/255.0, alpha: CGFloat(data[4*index+3])/255.0)
}
default:
// unsupported format
return UIColor.clear
}
}
else
{
// Something didnt load properly or has been destroyed
return UIColor.clear
}
}
}
Итак, приведенные выше два класса являются примером выполнения кода, вызывающего проблему
let image = UIImage(named: "SomeImage")!
let croppedImage = ImageCrop.crop(image: image, rect: CGRect(x: 0, y: 0, width: 100, height: 100))
let imagePixelHelper = ImagePixelHelper(image: croppedImage)
for x in 0 ... Int(croppedImage.size.width)
{
for y in 0 ... Int(croppedImage.size.height)
{
let colour = imagePixelHelper.getPixelValueFromImage(pixelX: x, pixelY: y)
print(colour)
}
}
Это будет печатать только UIColour.CLEAR, поскольку croppedImage - неизвестный формат / больший размер, чем мы ожидали. Затем в отладчике, просматривающем переменную imagePixelHelper, ожидаемые длины массивов данных для изображения 100 * 100 следующие:
pectedLengthRGBA = 40000
Тогда фактическая найденная длина массива данных, массива данных изображения, составляет 41600, что больше ожидаемого, вот в чем проблема. Немного покопавшись в быстрых документах, мы обнаружили, что можем использовать неправильный тип графического контекста для обрезки нашего изображения и что вместо этого мы предлагаем использовать UIGraphicsImageRenderer. Но я изо всех сил пытаюсь найти / создать реализацию для обрезки, которая работает.
TLDR: Короче говоря, если изображение сначала обрезается с использованием класса ImageCrop, оно ломает ImagePixelHelper, поскольку размер массива данных изображения несоответствовать ожидаемому расчетному размеру.