Подтвердить что ты не робот

Цвета iOS неверны при сохранении анимированных Gif

У меня такая очень странная проблема. Я создаю анимированные gifs от UIImages, и большую часть времени они выходят правильно. Однако, когда я начинаю получать изображения большего размера, мои цвета начинают исчезать. Например, если я делаю 4-х кратное 32 x 32-пиксельное изображение с не более чем 10 цветами, никаких проблем. Если я масштабирую одно изображение до 832 x 832, я теряю розовый цвет, а коричневый становится зеленым.

@1x 32 x 32

введите описание изображения здесь

@10x 320 x 320

введите описание изображения здесь

@26x 832 x 832

введите описание изображения здесь

Вот код, который я использую для создания 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

4b9b3361

Ответ 1

Кажется, что отключение глобальной цветовой карты устраняет проблему:

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, которая могла бы корректно обрабатывать преобразование цветов.

Ответ 2

Далее, здесь появляется еще несколько предпосылок о том, что происходит квантование. Если вы запускаете выход GIF через imagemagick, чтобы извлечь цветовые палитры для версии с глобальной цветовой картой и цветной картой для каждого кадра, есть некоторые сведения о корне проблемы:

Версия с GLOBAL color map: $ 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

Таким образом, в первом отсутствует коричневый и розовый, цвета с 246 и 113 в красном канале вообще не указаны, и они перечислены правильно на гистограмме (предположительно повторяющейся для каждого кадра в более длинный вывод) для версии цветной карты для каждого кадра.

Это доказательство того, что палитра генерируется неправильно в GIF, что мы видим легко с нашими глазами. Однако меня удивляет то, что глобальная версия цветовой карты имеет повторяющиеся записи для нескольких цветов. Это указывает на довольно явную ошибку в квантовании палитры в ImageIO. Не должно быть дубликатов записей в ограниченной цветовой палитре.

Короче: не полагайтесь на Core Graphics для квантования 24-битных изображений RGB. Предварительно запрограммируйте их заранее, прежде чем отправлять их в ImageIO и отключать глобальные цветовые карты. Если проблема все еще проявляется, тогда пишущая палитра ImageIO нарушена, и вы должны использовать другую библиотеку выходных файлов GIF