Створити та експортувати анімований GIF через iOS?


74

У мене є серія зображень, налаштованих користувачем, у програмі iOS, які анімуються у простому, покроковому стилі фліп-книги.

Моє запитання таке: чи є спосіб дозволити користувачам експортувати свою анімацію як анімований gif? В ідеалі я хотів би дозволити їм надіслати електронною поштою, соціальним ресурсом (T / FB) або (найгірший випадок ..) зберегти анімований gif у папку своїх документів для отримання через iTunes.

Я знаю, як зберегти .png у фототеці, і знайшов спосіб записати анімацію як файл QT ( http://www.cimgf.com/2009/02/03/record-your-core-animation -animation / ), але я не знайшов способу просто вигнати звичайний старий анімований gif. Мені щось не вистачає в Core Animation чи ще десь? Чи є якісь підходи, рамки чи ресурси, які хтось може порекомендувати? Вибачте, якщо питання занадто загальне - намагається знайти вихідну точку.

Відповіді:


161

Ви можете створити анімований GIF, використовуючи фреймворк вводу-виводу зображень (який є частиною SDK для iOS). Ви також захочете включити MobileCoreServicesструктуру, яка визначає константу типу GIF. Вам потрібно додати ці фреймворки до своєї цілі та імпортувати їх заголовки у файл, де ви хочете створити анімований GIF, наприклад:

#import <ImageIO/ImageIO.h>
#import <MobileCoreServices/MobileCoreServices.h>

Найпростіше пояснити на прикладі. Я покажу вам код, який я використовував для створення цього GIF на своєму iPhone 5:

анімований GIF, створений за показаним кодом

По-перше, ось допоміжна функція, яка приймає розмір і кут і повертає a UIImageчервоного диска під цим кутом:

static UIImage *frameImage(CGSize size, CGFloat radians) {
    UIGraphicsBeginImageContextWithOptions(size, YES, 1); {
        [[UIColor whiteColor] setFill];
        UIRectFill(CGRectInfinite);
        CGContextRef gc = UIGraphicsGetCurrentContext();
        CGContextTranslateCTM(gc, size.width / 2, size.height / 2);
        CGContextRotateCTM(gc, radians);
        CGContextTranslateCTM(gc, size.width / 4, 0);
        [[UIColor redColor] setFill];
        CGFloat w = size.width / 10;
        CGContextFillEllipseInRect(gc, CGRectMake(-w / 2, -w / 2, w, w));
    }
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

Тепер ми можемо створити GIF. Спочатку визначимо константу для кількості кадрів, оскільки вона нам потрібна двічі пізніше:

static void makeAnimatedGif(void) {
    static NSUInteger const kFrameCount = 16;

Нам потрібен словник властивостей, щоб вказати кількість повторень анімації:

    NSDictionary *fileProperties = @{
        (__bridge id)kCGImagePropertyGIFDictionary: @{
            (__bridge id)kCGImagePropertyGIFLoopCount: @0, // 0 means loop forever
        }
    };

І нам знадобиться ще один словник властивостей, який ми прикріпимо до кожного кадру, вказуючи, як довго цей кадр повинен відображатися:

    NSDictionary *frameProperties = @{
        (__bridge id)kCGImagePropertyGIFDictionary: @{
            (__bridge id)kCGImagePropertyGIFDelayTime: @0.02f, // a float (not double!) in seconds, rounded to centiseconds in the GIF data
        }
    };

Ми також створимо URL-адресу для GIF у нашому каталозі документів:

    NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:nil];
    NSURL *fileURL = [documentsDirectoryURL URLByAppendingPathComponent:@"animated.gif"];

Тепер ми можемо створити файл, CGImageDestinationякий пише GIF на вказану URL-адресу:

    CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef)fileURL, kUTTypeGIF, kFrameCount, NULL);
    CGImageDestinationSetProperties(destination, (__bridge CFDictionaryRef)fileProperties);

Я виявив , що передача в filePropertiesякості останнього аргументу CGImageDestinationCreateWithURLробить НЕ роботу. Ви повинні використовувати CGImageDestinationSetProperties.

Тепер ми можемо створювати та писати наші кадри:

    for (NSUInteger i = 0; i < kFrameCount; i++) {
        @autoreleasepool {
            UIImage *image = frameImage(CGSizeMake(300, 300), M_PI * 2 * i / kFrameCount);
            CGImageDestinationAddImage(destination, image.CGImage, (__bridge CFDictionaryRef)frameProperties);
        }
    }

Зверніть увагу, що ми передаємо словник властивостей кадру разом із кожним зображенням кадру.

Після того, як ми додали точно вказану кількість кадрів, ми доопрацьовуємо пункт призначення і звільняємо його:

    if (!CGImageDestinationFinalize(destination)) {
        NSLog(@"failed to finalize image destination");
    }
    CFRelease(destination);

    NSLog(@"url=%@", fileURL);
}

Якщо ви запустите це на симуляторі, ви можете скопіювати URL-адресу з консолі налагодження та вставити її у свій браузер, щоб побачити зображення. Якщо ви запускаєте його на пристрої, ви можете скористатися вікном Xcode Organizer, щоб завантажити пісочницю програми з пристрою та подивитися зображення. Або ви можете використовувати такий додаток, якiExplorer яка дозволяє безпосередньо переглядати файлову систему вашого пристрою. (Для цього не потрібен джейлбрейк.)

Я протестував це на своєму iPhone 5 під управлінням iOS 6.1, але я вважаю, що код повинен працювати ще до iOS 4.0.

Я вклав увесь код у цей суть для зручного копіювання.


1
Роб, я сьогодні працюю над твоєю технікою, але є велике обмеження. Система зберігає дані для всіх зображень у послідовності анімації в пам'яті до завершення завершення виклику. Я створюю кілька досить великих послідовностей GIF, і при 30 FPS не потрібно багато часу, щоб закінчитися пам'ять і вийти з ладу. Чи є спосіб буферувати кадри на диск? Я створюю свої CGImages з OpenGL і використовую постачальник даних. Існує форма постачальника даних, яка зчитує вміст із файлу. Цікаво, чи читало б це кожен кадр по черзі та випускало б його, коли він завершив GIF.
Duncan C

3
Дуже швидкий пошук та перевірка змушують мене думати, що Жираф може робити те, що ти хочеш.
пограбувати Mayoff

1
@robmayoff Привіт Роб, я використовую ваш код вже досить давно, але я виявив дивну помилку, я завжди отримую EXE_BAD_ACCESS на CGImageDestinationFinalize (призначення), якщо в послідовності введення є однакові кадри один біля одного. Будь-яка ідея, чому це відбувається?
XY

1
@Pascal Це системна помилка, ви не можете мати однакових кадрів у кінці вашої послідовності, тобто якщо у вас є n кадрів, n-й та n-1-й кадри не можуть бути однаковими. У підсумку я обрізав хвіст моєї послідовності для однакових кадрів. (І я використовую дуже наївний спосіб виявлення однакових кадрів, розмір файлу плюс позначка часу в тезі EXIF, дуже добре для мене спрацював ...)
XY

1
@kevin Перевірте це питання та його коментарі. Я не тестував, але у мене немає підстав сумніватися в цій інформації.
грабувати майофф

1

Для Свіфта 3

import Foundation
import UIKit
import ImageIO
import MobileCoreServices

extension UIImage {
    static func animatedGif(from images: [UIImage]) {
        let fileProperties: CFDictionary = [kCGImagePropertyGIFDictionary as String: [kCGImagePropertyGIFLoopCount as String: 0]]  as CFDictionary
        let frameProperties: CFDictionary = [kCGImagePropertyGIFDictionary as String: [(kCGImagePropertyGIFDelayTime as String): 1.0]] as CFDictionary

        let documentsDirectoryURL: URL? = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
        let fileURL: URL? = documentsDirectoryURL?.appendingPathComponent("animated.gif")

        if let url = fileURL as CFURL? {
            if let destination = CGImageDestinationCreateWithURL(url, kUTTypeGIF, images.count, nil) {
                CGImageDestinationSetProperties(destination, fileProperties)
                for image in images {
                    if let cgImage = image.cgImage {
                        CGImageDestinationAddImage(destination, cgImage, frameProperties)
                    }
                }
                if !CGImageDestinationFinalize(destination) {
                    print("Failed to finalize the image destination")
                }
                print("Url = \(fileURL)")
            }
        }
    }
}

Я перетворив його з наведеної вище відповіді . Сподіваюся, це допоможе.

Доступний як суть .

Редагування вітаються.


Цікавою альтернативою було б впровадження цього методу в розширення до Arrayде Element == UIImage.
Ніколас Міарі

Приємна ідея @NicolasMiari
Nikhil Manapure

0

Якщо ви шукаєте рішення Swift 3, ви можете поглянути на https://github.com/onmyway133/GifMagic . Він має EncoderіDecoder який збирає та розбирає gif-файл.

В принципі, ви повинні використовувати Image IOрамки з цими функціями CGImageDestinationCreateWithURL, CGImageDestinationSetProperties, CGImageDestinationAddImage,CGImageDestinationFinalize

Також із Swift https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html

Об'єкти Core Foundation, що повертаються з анотованих API, автоматично управляються пам'яттю в Swift - вам не потрібно самостійно викликати функції CFRetain, CFRelease або CFAutorelease.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.