Ich habe this example von Apple gefolgt, um ein sehr großes Bild zu verkleinern Ich bin downloading from a remote location. Ich habe den Code in Swift neu geschrieben. Es funktioniert anscheinend, aber wenn ich MTKTextureLoader.newTexture
rufe, stürzt die Anwendung mit einem EXC_BAD_ACCESS
in _loadCGImage
ab. Es gibt keinen anderen Hinweis, aber ich vermute, die Bilddaten wurden bereits freigegeben oder etwas ...Verkleinern sehr großes Bild zu MTLTexture
Alle Hinweise, warum würde es ohne eine richtige Fehlermeldung abstürzen?
Dies ist der Top-Level-Code ist
// this is an extension of MTKTextureLoader
// [...]
if let uiImage = UIImage(contentsOfFile: cachedFileURL.path) {
let maxDim : CGFloat = 8192
if uiImage.size.width > maxDim || uiImage.size.height > maxDim {
let scale = uiImage.size.width > maxDim ? maxDim/uiImage.size.width : maxDim/uiImage.size.height
if let cgImage = MTKTextureLoader.downsize(image: uiImage, scale: scale) {
return self.newTexture(with: cgImage, options: options, completionHandler: completionHandler)
} else {
anError = TextureError.CouldNotDownsample
}
} else {
return self.newTexture(withContentsOf: cachedFileURL, options: options, completionHandler: completionHandler)
}
}
Und die downsize Methode,
private static func downsize(image: UIImage, scale: CGFloat) -> CGImage? {
let destResolution = CGSize(width: Int(image.size.width * scale), height: Int(image.size.height * scale))
let kSourceImageTileSizeMB : CGFloat = 40.0 // The tile size will be (x)MB of uncompressed image data
let pixelsPerMB = 262144
let tileTotalPixels = kSourceImageTileSizeMB * CGFloat(pixelsPerMB)
let destSeemOverlap : CGFloat = 2.0 // the numbers of pixels to overlap the seems where tiles meet.
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
guard let destContext = CGContext(data: nil, width: Int(destResolution.width), height: Int(destResolution.height), bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace, bitmapInfo: bitmapInfo.rawValue) else {
NSLog("failed to create the output bitmap context!")
return nil
}
var sourceTile = CGRect()
sourceTile.size.width = image.size.width
sourceTile.size.height = floor(tileTotalPixels/sourceTile.size.width)
print("source tile size: \(sourceTile.size)")
sourceTile.origin.x = 0.0
// the output tile is the same proportions as the input tile, but
// scaled to image scale.
var destTile = CGRect()
destTile.size.width = destResolution.width
destTile.size.height = sourceTile.size.height * scale
destTile.origin.x = 0.0
print("dest tile size: \(destTile.size)")
// the source seem overlap is proportionate to the destination seem overlap.
// this is the amount of pixels to overlap each tile as we assemble the output image.
let sourceSeemOverlap : CGFloat = floor((destSeemOverlap/destResolution.height) * image.size.height)
print("dest seem overlap: \(destSeemOverlap), source seem overlap: \(sourceSeemOverlap)")
// calculate the number of read/write operations required to assemble the
// output image.
var iterations = Int(image.size.height/sourceTile.size.height)
// if tile height doesn't divide the image height evenly, add another iteration
// to account for the remaining pixels.
let remainder = Int(image.size.height) % Int(sourceTile.size.height)
if remainder > 0 {
iterations += 1
}
// add seem overlaps to the tiles, but save the original tile height for y coordinate calculations.
let sourceTileHeightMinusOverlap = sourceTile.size.height
sourceTile.size.height += sourceSeemOverlap
destTile.size.height += destSeemOverlap
print("beginning downsize. iterations: \(iterations), tile height: \(sourceTile.size.height), remainder height: \(remainder)")
for y in 0..<iterations {
// create an autorelease pool to catch calls to -autorelease made within the downsize loop.
autoreleasepool {
print("iteration \(y+1) of \(iterations)")
sourceTile.origin.y = CGFloat(y) * sourceTileHeightMinusOverlap + sourceSeemOverlap
destTile.origin.y = (destResolution.height) - (CGFloat(y + 1) * sourceTileHeightMinusOverlap * scale + destSeemOverlap)
// create a reference to the source image with its context clipped to the argument rect.
if let sourceTileImage = image.cgImage?.cropping(to: sourceTile) {
// if this is the last tile, its size may be smaller than the source tile height.
// adjust the dest tile size to account for that difference.
if y == iterations - 1 && remainder > 0 {
var dify = destTile.size.height
destTile.size.height = CGFloat(sourceTileImage.height) * scale
dify -= destTile.size.height
destTile.origin.y += dify
}
// read and write a tile sized portion of pixels from the input image to the output image.
destContext.draw(sourceTileImage, in: destTile, byTiling: false)
}
// !!! In the original LargeImageDownsizing code, it released the source image here !!!
// [image release];
// !!! I assume I don't need to do that in Swift?? !!!
/* while CGImageCreateWithImageInRect lazily loads just the image data defined by the argument rect,
that data is finally decoded from disk to mem when CGContextDrawImage is called. sourceTileImageRef
maintains internally a reference to the original image, and that original image both, houses and
caches that portion of decoded mem. Thus the following call to release the source image. */
// http://en.swifter.tips/autoreleasepool/
// drain will be called
// to free all objects that were sent -autorelease within the scope of this loop.
}
// !!! Original code reallocated the image here !!!
// we reallocate the source image after the pool is drained since UIImage -imageNamed
// returns us an autoreleased object.
}
print("downsize complete.")
// create a CGImage from the offscreen image context
return destContext.makeImage()
}
Edit:
Es stürzt ich jedes Mal versuchen, die MTLTexture
mit einem initialisieren CGImage
, auch wenn das Bild klein ist und in den Speicher passt. So scheint es in keinem Zusammenhang mit der Redimensionierung ... auch Dieser Code abstürzt,
func newTexture(with uiImage: UIImage, options: [String : NSObject]? = nil, completionHandler: @escaping MTKTextureLoaderCallback) {
if let cgImage = uiImage.cgImage {
return self.newTexture(with: cgImage, options: options, completionHandler: completionHandler)
} else {
completionHandler(nil, TextureError.CouldNotBeCreated)
}
}
bei ImageIO copyImageBlockSetWithOptions
.
Edit2: Workaround basierend auf der Antwort von warrenm: Rufen Sie newTexture(with: cgImage)
Sync anstelle von async. Zum Beispiel über die Funktion wird,
func newTexture(with uiImage: UIImage, options: [String : NSObject]? = nil, completionHandler: MTKTextureLoaderCallback) {
if let cgImage = uiImage.cgImage {
let tex = try? self.newTexture(with: cgImage, options: options)
completionHandler(tex, tex == nil ? TextureError.CouldNotBeCreated : nil)
} else {
completionHandler(nil, TextureError.CouldNotGetCGImage)
}
}
(Ich nenne tatsächlich die Synchronisierungsmethode Beachten Sie, dass ich die @escaping
da jetzt entfernt haben.)
Der verkleinern Code richtig war. Es funktioniert jetzt mit dieser Problemumgehung :)
Danke! Das hat perfekt funktioniert. Ich habe die Aufrufe von newTexture synchron gemacht, und sowohl der newTexture-with-uiImage als auch der Downsize-Code funktionieren jetzt :) Ich habe meine Frage so bearbeitet, dass sie die Problemumgehung enthält. Danke noch einmal! – endavid