保存动画GIF时iOS颜色不正确

问题描述:

我有这个很奇怪的问题。我从UIImages创建GIF动画,并且大多数时候它们都是正确的。但是,当我开始进入更大尺寸的图像时,我的颜色开始消失。例如,如果我做一个4帧32×32像素的图像,不超过10个颜色没有问题。如果我将相同的图像缩放至832 x 832,则会失去粉红色,棕色变为绿色。保存动画GIF时iOS颜色不正确

@ 1×32×32

enter image description here

@ 10×320×320

enter image description here

@ 26X 832 X 832

enter image description here

下面是代码我用它来创建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") 
    }   
} 

我已经把一个破发点右我打电话CGImageDestinationAddImage(destination, image.cgImage!, frameProperties as CFDictionary)前后图像是正确的色彩完美的罚款。我希望那里的人知道我错过了什么。

更新

这里是一个样本项目。请注意,虽然它在预览中没有动画,但它保存了一个动画gif,并且我在控制台中注销了图像的位置。

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

+0

问题是否仅在视网膜设备或非视网膜设备上出现?我最好的猜测是,它与图像尺寸无法应对更高分辨率有关。您是否尝试过创建较大尺寸的图片,然后将其缩小而不是以其他方式进行缩放? – Malik

+0

@Malik我从零开始使用UnsafeMutablePointer和上下文创建这些图像。为了回答这个问题,它发生在视网膜设备上,对于非视网膜没有任何线索,因为我没有人测试。至于放大或缩小,实际上并不是按比例缩放。我从头开始创建一个新的图像并重复像素。 –

+0

当你断点和图像看起来很好,你看'UIImage'或'CGImage'? –

看来,关闭全球彩色地图解决了这个问题:

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

注意,不同于PNG,GIF格式只能使用256彩色地图,缺乏透明度。对于动画GIF,可以是全局或每帧彩色地图。

不幸的是,Core Graphics不允许我们直接使用颜色映射,因此GIF编码时会自动进行颜色转换。

看起来关闭全局色彩图就是所需要的。同样使用kCGImagePropertyGIFImageColorMap为每个帧明确设置颜色映射也可能会起作用。

由于这似乎并不可靠地工作,让我们创建我们自己的彩色地图,每帧:

struct Color : Hashable { 
    let red: UInt8 
    let green: UInt8 
    let blue: UInt8 

    var hashValue: Int { 
     return Int(red) + Int(green) + Int(blue) 
    } 

    public static func ==(lhs: Color, rhs: Color) -> Bool { 
     return [lhs.red, lhs.green, lhs.blue] == [rhs.red, rhs.green, rhs.blue] 
    } 
} 

struct ColorMap { 
    var colors = Set<Color>() 

    var exported: Data { 
     let data = Array(colors) 
      .map { [$0.red, $0.green, $0.blue] } 
      .joined() 

     return Data(bytes: Array(data)) 
    } 
} 

现在让我们更新方法:

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(red: 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)) 

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

当然,只有在颜色数少于256的情况下,这才会起作用。我真的会推荐一个自定义的GIF库,可以正确处理颜色转换。

+0

这是正确的,它解决了这个问题。仅供参考,但设置明确的每帧彩色贴图似乎不受ImageIO支持。我尝试过,没有任何区别。看起来,当传入UIImage作为源时,它总是将所有帧中的颜色自动量化为单个调色板(最多256色或255 +透明色)。 它似乎关闭了全局色彩映射,这样就迫使它单独量化每个图像帧,解决任何CoreGraphics bug /毛刺,这是与较大的图像有关。 –

+0

为了更加智能的色彩量化处理,您需要先看看量化源图像,然后使用适合您的任何逻辑。附注:大概这只是测试代码问题,但是那些用于填充/清除图像的三重嵌套循环是非常不理想的方式。阵列填满FTW! –

+0

@MarcPalmer是的,代码远不是最优的。我可能会选择一个外部的GIF编码库。核心图形显然不适合与GIF一起使用。 – Sulthan

以下是关于量化失败的更多背景信息。如果通过imagemagick运行GIF输出以提取具有全局色图与每帧色图的版本的调色板,则可以深入了解问题的根源:

具有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)

与每帧色彩映射的版本: $ 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

因此,第一个缺少棕色和粉红色,在红色通道246113颜色未列出所有,并且这些在直方图中正确列出(大概重复对于较长输出中的每一帧,编辑)针对每帧彩色映射版本。

这证明调色板在GIF中生成不正确,这是我们用眼睛容易看到的。然而,我想知道的是,全球色彩地图版本有重复条目几种颜色。这指出ImageIO中调色板量化的一个非常明显的错误。在有限的调色板中不应该有重复的条目。

简而言之:不要依赖Core Graphics来定量你的24位RGB图像。在将它们发送到ImageIO并关闭全局色彩映射之前,预先对它们进行量化。如果问题仍然存在,那么ImageIO调色板写入已损坏,并且您应该使用不同的GIF输出库

+0

谢谢你看着这个,但我迷路了“总之:不要依靠核心图形来量化你的24位RGB图像,在将它们发送到ImageIO并关闭全局彩色地图之前预先对它们进行量化。“如果需要创建使用的颜色数组不是一个问题,但我对你的意思是什么或者如何使用ImageIO来做这件事感到迷茫。 –

+0

查看@Sulthan的回答。这就是我的意思。把你自己的颜色图放在一起,但在你做之前,确保所有的UIImage都具有

+0

为他的回答添加了评论,为什么这不是问题。再次感谢你的帮助。 –