неожиданное количество байтов в UIImage - PullRequest
0 голосов
/ 28 октября 2019

Таким образом, проблема получает 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, поскольку размер массива данных изображения несоответствовать ожидаемому расчетному размеру.

...