2

I have an iOS app which allows uploading an image to an Ubuntu server (with Apache webserver) using the Swift function below:

private func uploadPhoto(image:UIImage) {
    let imageData = image.jpegData(compressionQuality: 0.6)
    if imageData == nil {
        NSLog("Image Data is nil")
    } else {
        let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String
        let url = URL(string: "https://example.com/myapi.php")!
        var request = URLRequest(url: url)
        request.addValue("application/json", forHTTPHeaderField: "Accept")
        request.httpMethod = "POST"
        
        let boundary = NSUUID().uuidString
        request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
        
        let body = NSMutableData()
        // Text Parameter: Action
        body.append(NSString(format: "--%@\r\n", boundary).data(using: String.Encoding.utf8.rawValue)!)
        body.append(NSString(format: "Content-Disposition: form-data; name=\"action\"\r\n\r\n").data(using: String.Encoding.utf8.rawValue)!)
        body.append(NSString(format: "some_text_data\r\n").data(using: String.Encoding.utf8.rawValue)!)
        
        // Text Parameter: Build Number
        body.append(NSString(format: "--%@\r\n", boundary).data(using: String.Encoding.utf8.rawValue)!)
        body.append(NSString(format: "Content-Disposition: form-data; name=\"build\"\r\n\r\n").data(using: String.Encoding.utf8.rawValue)!)
        body.append(NSString(format: ("\(buildNumber ?? "99999")\r\n" as NSString)).data(using: String.Encoding.utf8.rawValue)!)
        
        // File Parameter
        body.append(NSString(format: "--%@\r\n", boundary).data(using: String.Encoding.utf8.rawValue)!)
        body.append(NSString(format:"Content-Disposition: form-data; name=\"the_img\"; filename=\"image.jpg\"\r\n").data(using: String.Encoding.utf8.rawValue)!)
        body.append(NSString(format: "Content-Type: application/octet-stream\r\n\r\n").data(using: String.Encoding.utf8.rawValue)!)
        body.append(imageData!)
        body.append(NSString(format: "\r\n--%@--\r\n", boundary).data(using: String.Encoding.utf8.rawValue)!)
        
        request.httpBody = body as Data
        
        let task = URLSession.shared.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
            guard error == nil else {
                return
            }
            
            guard let data = data else {
                return
            }
            
            do {
                //create json object from data
                if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
                    print(json)
                }
            } catch let error {
                print(error.localizedDescription)
            }
        })
        task.resume()
    }
}

The upload function works fine, and the images are uploaded to the server without errors. However, some images uploaded (around 1 in 100 images) will have green lines on the thumbnail (shown in macOS 10.15.6 Finder's quick preview) of the uploaded images; seems like the JPEG is corrupted. Please see an example screen shot below:

screenshot of quick preview

But the image itself seems to have no problem:

original photo

Why does that happen? If the image.jpegData(compressionQuality:) is set to 1.0 (no compression), this issue does not happen at all. Am I doing the image compression incorrectly?

Raptor
  • 53,206
  • 45
  • 230
  • 366
  • I don't understand. If the images are uploaded to an Ubuntu server from an iOS app, how are you viewing their thumbnails "in _macOS_ 10.15.6 Finder's quick preview"? – Sweeper Sep 22 '20 at 03:11
  • I download the image from Ubuntu server to my desktop iMac. – Raptor Sep 22 '20 at 04:33

2 Answers2

2

You will need to go through the whole flow of your image and see where exactly this corruption happens.

From what you posted you have an UIImage which is an in-memory representation of the image. You claim that this image looks well.

The next step is encoding the image into a standard format JPEG using image.jpegData(compressionQuality: 0.6). The result is data that is writable to file and can be opened by any of very many applications that support opening JPEG images. I suggest that you do try and write it to some file (Data has a write option of use FileManager.default), or alternatively you could just quickly try and send it to your computer directly using activity controller.

If this step produces corrupt image then there is a problem with encoder on iOS which I hope is not the case. But if so then please file a bug report and try using a different compression quality or different format (PNG if possible).

After this part you inject raw JPEG data into the body of your HTTP request. There is nothing special about it. The data should be delivered to server as they are and chances to make a corruption in transfer are too close to zero to witness a corruption in a lifetime. So after this part you need to check what is going on on server.

If nothing is happening on server then downloading your JPEG should produce exactly the same data as the data you sent to server. You can check that by comparing your data imageData with the downloaded data (Simply use try! Data(contentsOf: URL(string: "your URL here")!)).

If data is not the same (which I would expect by now) you should analyze what is happening on server side. It seems strange that this corruption would happen but it is possible that that there are some incompatible settings. Sometimes the solution is very unintuitive; like for instance that you need to send mime type with your request as well. I think it should be image/jpeg in your case.

Also from the anomaly we are seeing I would suspect that there is a compatibility issue with "bytes per row". Sometimes for performance reasons additional padding is added at the end of each "line". When not ignored the padding is then carried into next line. In your corrupt image you can see that the first line is OK while the second one starts with a green anomaly. The third line moves the anomaly forward by another equal length. Not sure if this information helps you in any say but it is a common issue when dealing with graphics textures for instance when using openGL.

Matic Oblak
  • 16,318
  • 3
  • 24
  • 43
  • Thanks for the detailed answer. I guess I shall start my debug journey right away. Seems it is pretty tough to debug this. – Raptor Sep 22 '20 at 05:32
  • My deepest respect for the answer; though would like to note that the first line isn't ok as well - the yellow "ray" starts there (from the top part of the bottle). And the colors are messed up all over the image. Anyway the described method to nail the bug down is the best I could imagine – nrx Sep 22 '20 at 08:27
  • @nrx you are correct, thank You. I did see there are multiple other anomalies; the yellow and then some blue and some red artifacts. It is really beyond me what is causing them. But if I had to guess I would say that this is the part of compression quality. Since compression packs chunks together it would make sense to assume that a single "pixel" mistake effects the whole area. We can even observe some symmetry of artifacts around the two green lines. But again, this is just me guessing. – Matic Oblak Sep 22 '20 at 11:29
1

I'd start with changing the mime type to image/jpeg from application/octet-stream (as Matic Oblak suggested in his detailed answer). Have had issues myself with image upload because of it.

nrx
  • 350
  • 4
  • 10