Я пытаюсь исправить проблему с производительностью при создании GIF с большим количеством кадров. Например, некоторые GIF могут содержать > 1200 кадров. С моим текущим кодом у меня заканчивается память. Я пытаюсь выяснить, как это решить; это можно сделать партиями? Моя первая идея заключалась в том, чтобы было возможно объединять изображения вместе, но я не думаю, что существует способ для этого или как GIF создаются инфраструктурой ImageIO
. Было бы неплохо, если бы существовал множественный метод CGImageDestinationAddImages
, но его нет, поэтому я теряюсь на том, как его решить. Я ценю любую помощь. Извините заранее за длинный код, но я чувствовал, что необходимо показать пошаговое создание GIF.
Допустимо, что я могу сделать видеофайл вместо GIF, пока возможны разные задержки в формате GIF в видео, и запись не займет столько времени, сколько сумма всех анимаций в каждом кадре.
Примечание: переход к последнему заголовку обновления ниже, чтобы пропустить предысторию.
Обновления 1 - 6:
Фиксация потока зафиксирована с помощью GCD
, но проблема с памятью все еще остается. 100% загрузка процессора здесь не является проблемой, так как я показываю UIActivityIndicatorView
, пока выполняется работа. Использование метода drawViewHierarchyInRect
может быть более эффективным/быстрым, чем метод renderInContext
, однако я обнаружил, что вы не можете использовать метод drawViewHierarchyInRect
в фоновом потоке с свойством afterScreenUpdates
, установленным в YES; он блокирует поток.
Должен быть какой-то способ записи GIF в партиях. Я считаю, что я сузил проблему с памятью до: CGImageDestinationFinalize
Этот метод кажется довольно неэффективным для создания изображений с большим количеством кадров, поскольку все должно быть в памяти, чтобы записать весь образ. Я подтвердил это, потому что я использую небольшую память, захватывая отображаемые изображения слоя containerView и вызываю CGImageDestinationAddImage
.
В тот момент, когда я звоню CGImageDestinationFinalize
, счетчик памяти мгновенно всплывает; иногда до 2 ГБ на основе количества кадров. Объем требуемой памяти просто кажется сумасшедшим, чтобы создать GIF ~ 20-1000 КБ.
Обновление 2: Есть метод, который я нашел, который может пообещать некоторую надежду. Это:
CGImageDestinationCopyImageSource(CGImageDestinationRef idst,
CGImageSourceRef isrc, CFDictionaryRef options,CFErrorRef* err)
Моя новая идея заключается в том, что для каждых 10 или некоторых других произвольных # кадров я буду записывать их в пункт назначения, а затем в следующем цикле предыдущий завершенный пункт назначения с 10 кадрами станет моим новым источником. Однако есть проблема; прочитав документы, он утверждает следующее:
Losslessly copies the contents of the image source, 'isrc', to the * destination, 'idst'.
The image data will not be modified. No other images should be added to the image destination.
* CGImageDestinationFinalize() should not be called afterward -
* the result is saved to the destination when this function returns.
Это заставляет меня думать, что моя идея не сработает, но, увы, я попробовал. Продолжить обновление 3.
Обновление 3:
Я пробовал метод CGImageDestinationCopyImageSource
с моим обновленным кодом ниже, однако я всегда возвращаю изображение только с одним фреймом; это из-за документации, указанной в обновлении 2 выше, наиболее вероятно. Есть еще один способ, который можно попробовать: CGImageSourceCreateIncremental
Но я сомневаюсь, что это то, что мне нужно.
Кажется, мне нужен способ записи/добавления кадров GIF на диск пошагово, чтобы я мог очистить каждый новый фрагмент из памяти. Возможно, идеал CGImageDestinationCreateWithDataConsumer
с соответствующими обратными вызовами для сохранения данных постепенно?
Обновление 4:
Я начал пробовать метод CGImageDestinationCreateWithDataConsumer
, чтобы узнать, могу ли я управлять записью байтов при использовании NSFileHandle
, но опять же проблема заключается в том, что вызов CGImageDestinationFinalize
отправляет все байты в один снимок, который является Как и раньше, я исчерпал память. Мне действительно нужна помощь, чтобы решить эту проблему и предложите большую щедрость.
Обновление 5:
Я опубликовал большую щедрость. Я хотел бы увидеть некоторые блестящие решения без сторонней библиотеки или фреймворка для добавления необработанных байтов формата NSData
GIF друг к другу и поэтапно записать его на диск с помощью NSFileHandle
- по существу создавая GIF вручную. Или, если вы считаете, что есть решение, которое можно найти с помощью ImageIO
, как то, что я пробовал, это тоже удивительно. Swizzling, подклассы и т.д.
Обновление 6: Я изучал, как GIF создаются на самом низком уровне, и я написал небольшой тест, который соответствует тому, что я собираюсь с помощью помощи наград. Мне нужно захватить обработанный UIImage, получить из него байты, сжать его с помощью LZW и добавить байты вместе с некоторыми другими работами, такими как определение глобальной таблицы цветов. Источник информации: http://giflib.sourceforge.net/whatsinagif/bits_and_bytes.html.
Последнее обновление:
Я проводил всю неделю, исследуя это со всех сторон, чтобы понять, что именно происходит, чтобы создать достойные качества GIF на основе ограничений (например, 256 цветов). Я полагаю и предполагаю, что делает ImageIO
, создавая единый контекст растрового изображения под капотом со всеми кадрами изображений, объединенными как один, и выполняет квантование цвета на этом растровом изображении, чтобы создать единую глобальную таблицу цветов, которая будет использоваться в GIF. Использование шестнадцатеричного редактора на некоторых успешных GIF файлах, сделанных из ImageIO
, подтверждает, что у них есть глобальная таблица цветов и никогда не имеет локальной, если вы не установите ее для каждого фрейма самостоятельно. Цветовое квантование выполняется на этом огромном растровом изображении для создания цветовой палитры (опять же предполагая, но твердо веря).
У меня есть эта странная и сумасшедшая идея: образы рамок из моего приложения могут отличаться только на один цвет за кадр и еще лучше, я знаю, какие небольшие наборы цветов используют мое приложение. Первый/фоновый фрейм - это кадр, который содержит цвета, которые я не могу контролировать (пользовательский контент, такой как фотографии), поэтому я думаю, что я сниму это представление, а затем сниму другое представление с тем, что имеет известные цвета, которыми занимается мое приложение с и сделать это одним растровым контекстом, который я могу передать в обычные программы ImaegIO
GIF. Какое преимущество? Наилучшим образом, это получает его от ~ 1200 кадров до одного путем слияния двух изображений в одно изображение. ImageIO
выполнит свою работу на гораздо меньшем растровом изображении и выпишет один GIF с одним фреймом.
Теперь, что я могу сделать, чтобы создать реальный GIF файл в формате 1200? Я думаю, что я могу взять GIF с одним кадром и извлечь байты таблицы цветов, потому что они попадают между двумя блоками протокола GIF. Мне все равно придется создавать GIF вручную, но теперь мне не нужно вычислять цветовую палитру. Я буду красть палитру ImageIO
, подумал лучше, и использовал ее для моего байтового буфера. Мне все еще нужна реализация компрессора LZW с помощью помощи наград, но это должно быть намного проще, чем квантование цвета, которое может быть болезненно медленным. LZW тоже может быть слишком медленным, поэтому я не уверен, что он того стоит; не знаю, как LZW будет выполнять последовательно с ~ 1200 кадров.
Каковы ваши мысли?
@property (nonatomic, strong) NSFileHandle *outputHandle;
- (void)makeGIF
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),^
{
NSString *filePath = @"/Users/Test/Desktop/Test.gif";
[[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
self.outputHandle = [NSFileHandle fileHandleForWritingAtPath:filePath];
NSMutableData *openingData = [[NSMutableData alloc]init];
// GIF89a header
const uint8_t gif89aHeader [] = { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 };
[openingData appendBytes:gif89aHeader length:sizeof(gif89aHeader)];
const uint8_t screenDescriptor [] = { 0x0A, 0x00, 0x0A, 0x00, 0x91, 0x00, 0x00 };
[openingData appendBytes:screenDescriptor length:sizeof(screenDescriptor)];
// Global color table
const uint8_t globalColorTable [] = { 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00 };
[openingData appendBytes:globalColorTable length:sizeof(globalColorTable)];
// 'Netscape 2.0' - Loop forever
const uint8_t applicationExtension [] = { 0x21, 0xFF, 0x0B, 0x4E, 0x45, 0x54, 0x53, 0x43, 0x41, 0x50, 0x45, 0x32, 0x2E, 0x30, 0x03, 0x01, 0x00, 0x00, 0x00 };
[openingData appendBytes:applicationExtension length:sizeof(applicationExtension)];
[self.outputHandle writeData:openingData];
for (NSUInteger i = 0; i < 1200; i++)
{
const uint8_t graphicsControl [] = { 0x21, 0xF9, 0x04, 0x04, 0x32, 0x00, 0x00, 0x00 };
NSMutableData *imageData = [[NSMutableData alloc]init];
[imageData appendBytes:graphicsControl length:sizeof(graphicsControl)];
const uint8_t imageDescriptor [] = { 0x2C, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x00 };
[imageData appendBytes:imageDescriptor length:sizeof(imageDescriptor)];
const uint8_t image [] = { 0x02, 0x16, 0x8C, 0x2D, 0x99, 0x87, 0x2A, 0x1C, 0xDC, 0x33, 0xA0, 0x02, 0x75, 0xEC, 0x95, 0xFA, 0xA8, 0xDE, 0x60, 0x8C, 0x04, 0x91, 0x4C, 0x01, 0x00 };
[imageData appendBytes:image length:sizeof(image)];
[self.outputHandle writeData:imageData];
}
NSMutableData *closingData = [[NSMutableData alloc]init];
const uint8_t appSignature [] = { 0x21, 0xFE, 0x02, 0x48, 0x69, 0x00 };
[closingData appendBytes:appSignature length:sizeof(appSignature)];
const uint8_t trailer [] = { 0x3B };
[closingData appendBytes:trailer length:sizeof(trailer)];
[self.outputHandle writeData:closingData];
[self.outputHandle closeFile];
self.outputHandle = nil;
dispatch_async(dispatch_get_main_queue(),^
{
// Get back to main thread and do something with the GIF
});
});
}
- (UIImage *)getImage
{
// Read question 'Update 1' to see why I'm not using the
// drawViewHierarchyInRect method
UIGraphicsBeginImageContextWithOptions(self.containerView.bounds.size, NO, 1.0);
[self.containerView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *snapShot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// Shaves exported gif size considerably
NSData *data = UIImageJPEGRepresentation(snapShot, 1.0);
return [UIImage imageWithData:data];
}