Los colors de iOS son incorrectos al save GIF animado

Estoy teniendo este extraño problema. Estoy creando gifs animados de UIImages y la mayoría de las veces salen correctos. Sin embargo, cuando empiezo a entrar en imágenes de mayor tamaño, mis colors comienzan a desaparecer. Por ejemplo, si hago una image de 32 x 32 píxeles de 4 cuadros con no más de 10 colors, no hay problema. Si escalar la misma image hasta 832 x 832, perderé un color rosa y mi marrón se pondrá verde.

@ 1x 32 x 32

introduzca la descripción de la imagen aquí

@ 10x 320 x 320

introduzca la descripción de la imagen aquí

@ 26×832 x 832

introduzca la descripción de la imagen aquí

Aquí está el código que uso para crear el gif …

var kFrameCount = 0 for smdLayer in drawingToUse!.layers{ if !smdLayer.hidden { kFrameCount += 1 } } let loopingProperty = [String(kCGImagePropertyGIFLoopCount): 0] let fileProperties: [String: AnyObject] = [String(kCGImagePropertyGIFDictionary): loopingProperty as AnyObject]; let frameProperty = [String(kCGImagePropertyGIFDelayTime): Float(speedLabel.text!)!] let frameProperties: [String: AnyObject] = [String(kCGImagePropertyGIFDictionary): frameProperty as AnyObject]; let documentsDirectoryPath = "file://\(NSTemporaryDirectory())" if let documentsDirectoryURL = URL(string: documentsDirectoryPath){ let fileURL = documentsDirectoryURL.appendingPathComponent("\(drawing.name)\(getScaleString()).gif") let destination = CGImageDestinationCreateWithURL(fileURL as CFURL, kUTTypeGIF, kFrameCount, nil)! CGImageDestinationSetProperties(destination, fileProperties as CFDictionary); for smdLayer in drawingToUse!.layers{ if !smdLayer.hidden{ let image = UIImage(smdLayer: smdLayer, alphaBlend: useAlphaLayers, backgroundColor: backgroundColorButton.backgroundColor!, scale: scale) CGImageDestinationAddImage(destination, image.cgImage!, frameProperties as CFDictionary) } } if (!CGImageDestinationFinalize(destination)) { print("failed to finalize image destination") } } 

He puesto un punto de interrupción justo antes de llamar a CGImageDestinationAddImage(destination, image.cgImage!, frameProperties as CFDictionary) y la image está perfectamente bien con los colors correctos. Espero que alguien sepa lo que me falta.

Actualizar

Aquí hay un proyecto de ejemplo. Tenga en count que, aunque no está animado en la vista previa, guarda un gif animado y desconecto la location de la image en la console.

https://www.dropbox.com/s/pb52awaj8w3amyz/gifTest.zip?dl=0

Parece que apagar el map de color global soluciona el problema:

 let loopingProperty: [String: AnyObject] = [ kCGImagePropertyGIFLoopCount as String: 0 as NSNumber, kCGImagePropertyGIFHasGlobalColorMap as String: false as NSNumber ] 

Tenga en count que a diferencia de los PNG, los GIF pueden usar solo un map de 256 colors, sin transparencia. Para los GIF animados, puede haber un map de color global o por marco.

Desafortunadamente, Core Graphics no nos permite trabajar con maps de color directamente, por lo tanto, hay una conversión de color automática cuando el GIF está codificado.

Parece que apagar el map de color global es todo lo que se necesita. Además, configurar el map de colors explícitamente para cada fotogtwig utilizando kCGImagePropertyGIFImageColorMap probablemente funcionaría también.

Dado que esto parece no funcionar de manera confiable, creamos nuestro propio map de colors para cada marco:

 struct Color : Hashable { let networking: UInt8 let green: UInt8 let blue: UInt8 var hashValue: Int { return Int(networking) + Int(green) + Int(blue) } public static func ==(lhs: Color, rhs: Color) -> Bool { return [lhs.networking, lhs.green, lhs.blue] == [rhs.networking, rhs.green, rhs.blue] } } struct ColorMap { var colors = Set<Color>() var exported: Data { let data = Array(colors) .map { [$0.networking, $0.green, $0.blue] } .joined() return Data(bytes: Array(data)) } } 

Ahora vamos a actualizar nuestros methods:

 func getScaledImages(_ scale: Int) -> [(CGImage, ColorMap)] { var sourceImages = [UIImage]() var result: [(CGImage, ColorMap)] = [] ... var colorMap = ColorMap() let pixelData = imageRef.dataProvider!.data let rawData: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData) for y in 0 ..< imageRef.height{ for _ in 0 ..< scale { for x in 0 ..< imageRef.width{ let offset = y * imageRef.width * 4 + x * 4 let color = Color(networking: rawData[offset], green: rawData[offset + 1], blue: rawData[offset + 2]) colorMap.colors.insert(color) for _ in 0 ..< scale { pixelPointer[byteIndex] = rawData[offset] pixelPointer[byteIndex+1] = rawData[offset+1] pixelPointer[byteIndex+2] = rawData[offset+2] pixelPointer[byteIndex+3] = rawData[offset+3] byteIndex += 4 } } } } let cgImage = context.makeImage()! result.append((cgImage, colorMap)) 

y

 func createAnimatedGifFromImages(_ images: [(CGImage, ColorMap)]) -> URL { ... for (image, colorMap) in images { let frameProperties: [String: AnyObject] = [ String(kCGImagePropertyGIFDelayTime): 0.2 as NSNumber, String(kCGImagePropertyGIFImageColorMap): colorMap.exported as NSData ] let properties: [String: AnyObject] = [ String(kCGImagePropertyGIFDictionary): frameProperties as AnyObject ]; CGImageDestinationAddImage(destination, image, properties as CFDictionary); } 

Por supuesto, esto funcionará solo si la cantidad de colors es inferior a 256. Realmente recomiendo una biblioteca GIF personalizada que pueda manejar la conversión de color correctamente.

A continuación, he aquí algunos antecedentes sobre la falla de cuantificación que está ocurriendo. Si ejecuta la salida de GIF a través de imagemgick para extraer las paletas de colors para la versión con un map de color global frente a un map de color por cuadro, hay una idea de la raíz del problema:

La versión con map de color GLOBAL: $ convert test.gif -format %c -depth 8 histogram:info:- 28392: ( 0, 0, 0,255) #000000FF black 240656: ( 71,162, 58,255) #47A23AFF srgba(71,162,58,1) 422500: (147,221,253,255) #93DDFDFF srgba(147,221,253,1) 676: (255,255,255,255) #FFFFFFFF white 2704: ( 71,162, 58,255) #47A23AFF srgba(71,162,58,1) 676: (147,221,253,255) #93DDFDFF srgba(147,221,253,1) 2704: ( 71,162, 58,255) #47A23AFF srgba(71,162,58,1) 676: (147,221,253,255) #93DDFDFF srgba(147,221,253,1) 2704: ( 71,162, 58,255) #47A23AFF srgba(71,162,58,1) 676: (147,221,253,255) #93DDFDFF srgba(147,221,253,1)

La versión con maps de color por cuadro: $ convert test.gif -format %c -depth 8 histogram:info:- 28392: ( 0, 0, 0,255) #000000FF black 237952: ( 71,163, 59,255) #47A33BFF srgba(71,163,59,1) 2704: (113, 78, 0,255) #714E00FF srgba(113,78,0,1) 421824: (147,221,253,255) #93DDFDFF srgba(147,221,253,1) 676: (246, 81,249,255) #F651F9FF srgba(246,81,249,1) 676: (255,255,255,255) #FFFFFFFF white 28392: ( 0, 0, 0,255) #000000FF black 237952: ( 71,163, 59,255) #47A33BFF srgba(71,163,59,1) 2704: (113, 78, 0,255) #714E00FF srgba(113,78,0,1) 421824: (147,221,253,255) #93DDFDFF srgba(147,221,253,1) 676: (246, 81,249,255) #F651F9FF srgba(246,81,249,1) 676: (255,255,255,255) #FFFFFFFF white 28392: ( 0, 0, 0,255) #000000FF black 237952: ( 71,163, 59,255) #47A33BFF srgba(71,163,59,1) 2704: (113, 78, 0,255) #714E00FF srgba(113,78,0,1) 421824: (147,221,253,255) #93DDFDFF srgba(147,221,253,1) 676: (246, 81,249,255) #F651F9FF srgba(246,81,249,1) 676: (255,255,255,255) #FFFFFFFF white 28392: ( 0, 0, 0,255) #000000FF black 237952: ( 71,163, 59,255) #47A33BFF srgba(71,163,59,1) 2704: (113, 78, 0,255) #714E00FF srgba(113,78,0,1) 421824: (147,221,253,255) #93DDFDFF srgba(147,221,253,1) 676: (246, 81,249,255) #F651F9FF srgba(246,81,249,1) 676: (255,255,255,255) #FFFFFFFF white $ convert test.gif -format %c -depth 8 histogram:info:- 28392: ( 0, 0, 0,255) #000000FF black 237952: ( 71,163, 59,255) #47A33BFF srgba(71,163,59,1) 2704: (113, 78, 0,255) #714E00FF srgba(113,78,0,1) 421824: (147,221,253,255) #93DDFDFF srgba(147,221,253,1) 676: (246, 81,249,255) #F651F9FF srgba(246,81,249,1) 676: (255,255,255,255) #FFFFFFFF white 28392: ( 0, 0, 0,255) #000000FF black 237952: ( 71,163, 59,255) #47A33BFF srgba(71,163,59,1) 2704: (113, 78, 0,255) #714E00FF srgba(113,78,0,1) 421824: (147,221,253,255) #93DDFDFF srgba(147,221,253,1) 676: (246, 81,249,255) #F651F9FF srgba(246,81,249,1) 676: (255,255,255,255) #FFFFFFFF white 28392: ( 0, 0, 0,255) #000000FF black 237952: ( 71,163, 59,255) #47A33BFF srgba(71,163,59,1) 2704: (113, 78, 0,255) #714E00FF srgba(113,78,0,1) 421824: (147,221,253,255) #93DDFDFF srgba(147,221,253,1) 676: (246, 81,249,255) #F651F9FF srgba(246,81,249,1) 676: (255,255,255,255) #FFFFFFFF white 28392: ( 0, 0, 0,255) #000000FF black 237952: ( 71,163, 59,255) #47A33BFF srgba(71,163,59,1) 2704: (113, 78, 0,255) #714E00FF srgba(113,78,0,1) 421824: (147,221,253,255) #93DDFDFF srgba(147,221,253,1) 676: (246, 81,249,255) #F651F9FF srgba(246,81,249,1) 676: (255,255,255,255) #FFFFFFFF white

Entonces, el primero no está en marrón y rosa, los colors con 246 y 113 en el canal rojo no figuran en la list, y estos se enumeran correctamente en el histogtwig (presumiblemente se repite para cada fotogtwig en el resultado más largo) versión de map de color de cuadro.

Esto es una testing de que la paleta se genera incorrectamente en el GIF, que es lo que vemos fácilmente con nuestros ojos. Sin embargo, lo que me hace pensar es que la versión global del map de colors tiene inputs duplicadas para varios colors. Esto apunta a un error bastante claro en la cuantificación de la paleta en ImageIO. No debe haber inputs duplicadas en una paleta de colors limitada.

En resumen: no confíe en Core Graphics para cuantificar sus imágenes RGB de 24 bits. Precanifíquelos por adelantado antes de enviarlos a ImageIO y desactive los maps de color globales. Si el problema aún se manifiesta, la escritura de la paleta ImageIO se interrumpe y debería usar una biblioteca de salida GIF diferente