I am trying to generate HEIF output files but get strange diagonal lines when I do so. None of the other output formats exhibit these strange lines. See the image below. The problem appears to be cropping but the errors only show in the HEIF output files.
I can replicate this with the following playground code which you can download from here https://duncangroenewald.com/files/SampleApps/CoreImageHEIFExporterBug-2.zip
I have a bug report with Apple and they mentioned "sub-pixel errors" - I have no idea what that is or why these would manifest only with the heif output format or how to prevent them - any ideas or does this just seem like a bug in the Apple code?
EDIT. To be clear this is not a playground issue since the problem occurs in a macOS application.
I suspect this must be a CIContext bug when generating HEIF output since it only manifests with heif output files. However I know nothing about sub-pixel errors or why Apple think they should be relevant for this issue. Perhaps someone with more Core Image knowledge might be able to suggest some possible solutions.
import Cocoa
let files = ["/Users/duncangroenewald/Development/Playgrounds/CoreImageHEIFExporterBug/DSC02022.ARW",
"/Users/duncangroenewald/Development/Playgrounds/CoreImageHEIFExporterBug/DSC02018.ARW"
]
let destFilePath = "/Users/duncangroenewald/Development/Playgrounds/CoreImageHEIFExporterBug"
let destUrl = URL(fileURLWithPath: destFilePath)
let exporter = Exporter()
var crops = [CGRect(x: 0, y: 0, width: 5310, height: 3212),
CGRect(x: 0, y: 0, width: 5311, height: 3212),
CGRect(x: 0, y: 0, width: 5312, height: 3212),
CGRect(x: 0, y: 0, width: 5313, height: 3212)]
for file in files {
exporter.processRAWFile(file, destPath: destFilePath, crops: crops)
}
print("Done!")
import AppKit
import CoreImage
import CoreImage.CIFilterBuiltins
import OSLog
public class Exporter {
var formats: [String] = ["heif", "png"]
// Get the CIContext
let ciContext = CIContext()
var imageOrientation: Int = 0
var imageNativeSize: CGSize = .zero
var crops: [CGRect] = [.zero]
public init() {
}
public func processRAWFile(_ path: String, destPath: String, crops: [CGRect]) {
print("processRAWFile XXX")
self.crops = crops
let url = URL(fileURLWithPath: path)
let filename = url.deletingPathExtension().lastPathComponent
let destDirUrl = URL(fileURLWithPath: destPath)
print("destDirUrl: \(destDirUrl)")
for crop in crops {
let cropName = "\(filename)_\(crop.width)x\(crop.height)_\(useDefaultRAW ? "default" : "")"
for format in formats {
let destUrl = destDirUrl.appendingPathComponent(cropName).appendingPathExtension(format)
print("destFile: \(destUrl)")
self.exportFile(url: url, destUrl: destUrl, crop: crop, format: format)
}
}
}
func exportFile(url: URL, destUrl: URL, crop: CGRect, format: String){
print("exportFile \(url)")
print("destFile: \(destUrl)")
guard let rawFilter = CIFilter(imageURL: url, options: nil), let outputImage = rawFilter.outputImage else {
print("Failed to load \(url.lastPathComponent) for exporting")
return
}
let processImage = self.crop(outputImage, rect: crop)
let options = [kCGImageDestinationLossyCompressionQuality as CIImageRepresentationOption: 1.0 as CGFloat]
let colorSpace = CGColorSpace(name: CGColorSpace.sRGB)!
let imgFormat = CIFormat.RGBA16
do {
switch format {
case "tiff":
try ciContext.writeTIFFRepresentation(of: processImage, to: destUrl, format: imgFormat, colorSpace: colorSpace, options: options)
case "jpeg":
try ciContext.writeJPEGRepresentation(of: processImage, to: destUrl, colorSpace: colorSpace, options: options)
case "png":
try ciContext.writePNGRepresentation(of: processImage, to: destUrl, format: imgFormat, colorSpace: colorSpace, options: options)
case "heif":
try ciContext.writeHEIFRepresentation(of: processImage, to: destUrl, format: imgFormat, colorSpace: colorSpace, options: options)
default:
try ciContext.writePNGRepresentation(of: processImage, to: destUrl, format: imgFormat, colorSpace: colorSpace, options: options)
}
} catch {
print("Error exporting \(format): \(error.localizedDescription)")
}
}
var useDefaultRAW: Bool = true
func crop(_ ciImage: CIImage, rect: CGRect)->CIImage {
if let cropped = self.cropFilter(ciImage, rect: rect) {
return cropped
} else {
print("Error cropping image !")
return ciImage
}
}
func cropFilter(_ input: CIImage, rect: CGRect) -> CIImage? {
// Getting a dashed border !!
guard let cropFilter = CIFilter(name: "CICrop") else {
print("Error no CICrop filter found !")
return input
}
let ciVect = CIVector(cgRect: rect)
cropFilter.setValue(input, forKey: kCIInputImageKey)
cropFilter.setValue(ciVect, forKey: "inputRectangle")
return cropFilter.value(forKey: kCIOutputImageKey) as? CIImage
}
}

The only way I could find to work it around was to render
CGImagethrough the context first and then re-createCIImagefromCGImage.EDIT