简体   繁体   中英

Swift: Filter every 4th item from array

I'm trying to implement an image algorithm that iterates the byte array of the image.

(I'm trying to replicate this in Swift... https://rosettacode.org/wiki/Percentage_difference_between_images#JavaScript )

However, I need to ignore the alpha byte.

I was trying to be clever about it but got to the point where I can no longer remove the 4th items from the arrays of bytes.

Is there an easy way of doing that?

func compareImages(image1: UIImage, image2: UIImage) -> Double {
    // get data from images
    guard let data1 = UIImageJPEGRepresentation(image1, 1),
        let data2 = UIImageJPEGRepresentation(image2, 1) else {
            return -1
    }

    // zip up byte arrays
    return zip([UInt8](data1), [UInt8](data2))
        // sum the difference of the bytes divided by 255
        .reduce(0.0) { $0 + Double(abs(Int32($1.0) - Int32($1.1))) / 255.0 }
        // divide by the number of rbg bytes
        / Double(image1.size.width * image1.size.height * 3)
}

This would do exactly what I needed if I was able to remove/ignore the 4th bytes from each array?

The other option is to stride the arrays 4 at a time like it does in the Javascript example linked to but I felt I preferred this method. :)

i think you can remove alpha with this

  1. enumerate to get pair (index, element)
  2. filter to remove alpha
  3. map to convert pair to only element

example code:

var array = [0,1,2,3,4,5,6,7,8,9]

array = array.enumerated().filter { index, element in
    return index % 4 != 3
}.map { index, element in
    return element
}

print(array) // [0,1,2,4,5,6,8,9]

Another, just a little bit more swifty way.

var foo = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]
var bar = [Int]()

for i in stride(from: 0, to: foo.count - 3, by: 4) {
    bar += foo[i..<i+3]
}

bar //[1, 2, 3, 1, 2, 3, 1, 2, 3]

Image data can be large, therefore I would avoid creating intermediate arrays only to remove each 4th element.

Your zip + reduce approach can be combined with enumerated() so that every 4th byte is ignored in the summation:

func rgbdiff(data1: [UInt8], data2: [UInt8], width: Int, height: Int) -> Double {
    return zip(data1, data2).enumerated().reduce(0.0) {
        $1.offset % 4 == 3 ? $0 : $0 + abs(Double($1.element.0) - Double($1.element.1))/255.0
    } / Double(width * height * 3)
}

Here it is assumed that data1 and data2 are arrays with the RGBA pixel data and both images have the same dimensions.

You could also work on Data values without conversion to arrays:

func rgbdiff(data1: Data, data2: Data, width: Int, height: Int) -> Double {
      // ... same function ...
}

because the Swift 3 Data is an Iterator of its bytes.

OK, for anyone who wants an update on filtering the array but also getting the correct pixel data...

I used a version of the answer from here... Get pixel data as array from UIImage/CGImage in swift

And @MartinR's answer to create the following two functions...

func pixelValues(fromCGImage imageRef: CGImage?) -> [UInt8]?
{
    var width = 0
    var height = 0
    var pixelValues: [UInt8]?

    if let imageRef = imageRef {
        width = imageRef.width
        height = imageRef.height
        let bitsPerComponent = imageRef.bitsPerComponent
        let bytesPerRow = imageRef.bytesPerRow
        let totalBytes = height * bytesPerRow
        let bitmapInfo = imageRef.bitmapInfo

        let colorSpace = CGColorSpaceCreateDeviceRGB()
        var intensities = [UInt8](repeating: 0, count: totalBytes)

        let contextRef = CGContext(data: &intensities, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)
        contextRef?.draw(imageRef, in: CGRect(x: 0.0, y: 0.0, width: CGFloat(width), height: CGFloat(height)))

        pixelValues = intensities
    }

    return pixelValues
}

func compareImages(image1: UIImage, image2: UIImage) -> Double? {
    guard let data1 = pixelValues(fromCGImage: image1.cgImage),
        let data2 = pixelValues(fromCGImage: image2.cgImage),
        data1.count == data2.count else {
            return nil
    }

    let width = Double(image1.size.width)
    let height = Double(image1.size.height)

    return zip(data1, data2)
        .enumerated()
        .reduce(0.0) {
            $1.offset % 4 == 3 ? $0 : $0 + abs(Double($1.element.0) - Double($1.element.1))
        }
        * 100 / (width * height * 3.0) / 255.0
}

I will be submitting this to the Rosetta website as soon as I work out how.

Swift 3:

var array = [0,1,2,3,4,5,6,7,8,9]

array = array.enumerated().flatMap { index, element in
    index % 4 != 3 ? element : nil
}

print(array) // [0,1,2,4,5,6,8,9]
let array1 = [1, 2, 3, 255, 5, 6, 7, 255, 8, 9, 10, 255]
let array2 = [1, 2, 3, 0, 5, 6, 7, 0, 8, 9, 10, 0]

let difference = zip(array1, array2)                   // Make one sequence from two arrays
    .enumerated()                                      // Assign each pair an index
    .filter({ $0.offset % 4 != 3 })                    // Strip away each 4th pair
    .map({ $0.element })                               // Discard indices
    .reduce(0, { $0 + Swift.abs($1.0 - $1.1) }) / 255  // Do the math

print(difference) // 0

Just make sure that both arrays have equal count of elements.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM