2017-03-02 80 views
2

我已按照蘋果公司的this example縮小圖像大小downloading from a remote location。我已經在Swift中重寫了代碼。它顯然是有效的,但是當我打電話給MTKTextureLoader.newTexture時,應用程序在_loadCGImage中崩潰了EXC_BAD_ACCESS。沒有其他提示,但我懷疑圖像數據已被釋放或什麼...將縮小圖像至MTLTexture

任何暗示爲什麼它會崩潰沒有任何適當的錯誤信息?

這是最高級別的代碼,

// 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) 
    } 
} 

這是縮小方法,

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() 
} 

編輯:

它崩潰每次我嘗試初始化MTLTexture與時間即使圖像很小並且適合內存,也是如此。因此,似乎無關的大小調整...此代碼還死機,

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) 
    } 
} 

在ImageIO的copyImageBlockSetWithOptions

編輯2: 基於warrenm的回答的變通方法:撥打newTexture(with: cgImage)同步而不是異步。舉例來說,上面的函數變,

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) 
    } 
} 

(注意,因爲現在我實際調用同步的方法,我已經刪除了@escaping。)

的精簡代碼是正確的。它現在適用於此解決方法:)

回答

2

這似乎是用於創建紋理的CGImage的生命週期的一個問題,它可能是MetalKit中的一個錯誤。

本質上,上下文返回的CGImage只保證一直存在,直到創建它的autorelease池的作用域結束。當您調用異步newTexture方法時,MTKTextureLoader將創建紋理的工作轉移到後臺線程,並且在其後備存儲已被釋放後,在其封閉自動釋放池範圍之外的CGImage上運行。

您可以通過在完成處理程序中捕獲圖像(這會導致ARC延長其壽命)或使用相應的同步紋理創建方法newTexture(with:options:)來解決此問題,該方法將保留在相關範圍中,直到紋理已完全初始化。

+0

謝謝!這工作完美。我對newTexture進行了同步調用,並且newTexture-with-uiImage和縮小尺寸代碼現在都可以工作:)我編輯了我的問題以包含解決方法。再次感謝! – endavid