Код загрузки / сохранения изображений Apple слегка изменяет мои изображения - PullRequest
0 голосов
/ 01 ноября 2018

Я пишу несколько тестов для обнаружения изменений в форматах изображений без потерь (начиная с PNG) и обнаруживаю, что в Linux и Windows механизмы загрузки изображений работают, как и ожидалось, - но на iOS (не пробовал на macOS) изображение данные всегда меняются очень незначительно, если я загружаю их из файла PNG на диске или сохраняю в файл PNG на диске с помощью методов Apple.

Если я создаю PNG с использованием любого количества инструментов (GIMP / Paint.NET / что угодно) и использую свой кросс-платформенный код чтения PNG для проверки каждого пикселя полученных в результате загруженных данных - это точно соответствует тому, что я сделал в инструменте (или программно сгенерированный с помощью моего кроссплатформенного кода для записи PNG.) Последующая перезагрузка в инструменты создания приводит к точно таким же компонентам RGBA8888.

Если я загружаю PNG с диска, используя Apple:

NSString* pPathToFile = nsStringFromStdString( sPathToFile );
UIImage* pImageFromDiskPNG = [UIImage imageWithContentsOfFile:pPathToFile];

... затем изучите полученные пиксели, они похожи, но не совпадают. Я ожидаю, что, как и на других платформах, данные будут идентичны.

Теперь, что интересно, если я загружаю данные из PNG, используя мой код и создавая UIImage с ним (используя некоторый код, который я показываю ниже), я могу использовать этот UIImage и отобразить его, скопировать его, что угодно, и если я изучить данные пикселей - это именно то, что я дал для начала (вот почему я думаю, что это часть сохранения загрузки, где Apple модифицирует данные изображения.)

Когда я приказываю ему сохранить то, что я знаю, как хороший UIImage с идеальными данными пикселей, а затем загрузить это сохраненное Apple изображение с помощью кода загрузки PNG, я вижу, что это не совсем те же данные. Я использовал несколько методов, с помощью которых Apple предлагает сохранить UIImage в PNG (в первую очередь UIImagePNGRepresentation.)

Единственное, о чем я могу подумать, это то, что Apple при загрузке или сохранении на iOS не поддерживает RGBA8888 и выполняет какие-то предварительные операции с альфа-каналом - я размышляю об этом, потому что, когда я впервые начал использовать код Я написал ниже, я выбирал

kCGImageAlphaLast

... вместо того, что мне в конечном итоге пришлось использовать

kCGImageAlphaPremultipliedLast

потому что первое почему-то не поддерживается на iOS.

У кого-нибудь есть опыт решения этой проблемы на iOS?

Ура!

Код, который я использую для передачи / извлечения данных RGBA8888 в UIImages и из них, приведен ниже:

    - (unsigned char *) convertUIImageToBitmapRGBA8:(UIImage*)image dataSize:(NSUInteger*)dataSize
    {
        CGImageRef imageRef = image.CGImage;

        // Create a bitmap context to draw the uiimage into
        CGContextRef context = [self newBitmapRGBA8ContextFromImage:imageRef];

        if(!context) {
            return NULL;
        }

        size_t width = CGImageGetWidth(imageRef);
        size_t height = CGImageGetHeight(imageRef);

        CGRect rect = CGRectMake(0, 0, width, height);

        // Draw image into the context to get the raw image data
        CGContextDrawImage(context, rect, imageRef);

        // Get a pointer to the data
        unsigned char *bitmapData = (unsigned char *)CGBitmapContextGetData(context);

        // Copy the data and release the memory (return memory allocated with new)
        size_t bytesPerRow = CGBitmapContextGetBytesPerRow(context);
        size_t bufferLength = bytesPerRow * height;

        unsigned char *newBitmap = NULL;

        if(bitmapData) {
            *dataSize = sizeof(unsigned char) * bytesPerRow * height;
            newBitmap = (unsigned char *)malloc(sizeof(unsigned char) * bytesPerRow * height);

            if(newBitmap) {    // Copy the data
                for(int i = 0; i < bufferLength; ++i) {
                    newBitmap[i] = bitmapData[i];
                }
            }

            free(bitmapData);

        } else {
            NSLog(@"Error getting bitmap pixel data\n");
        }

        CGContextRelease(context);

        return newBitmap;
    }


    - (CGContextRef) newBitmapRGBA8ContextFromImage:(CGImageRef) image
    {
        CGContextRef context = NULL;
        CGColorSpaceRef colorSpace;
        uint32_t *bitmapData;

        size_t bitsPerPixel = 32;
        size_t bitsPerComponent = 8;
        size_t bytesPerPixel = bitsPerPixel / bitsPerComponent;

        size_t width = CGImageGetWidth(image);
        size_t height = CGImageGetHeight(image);

        size_t bytesPerRow = width * bytesPerPixel;
        size_t bufferLength = bytesPerRow * height;

        colorSpace = CGColorSpaceCreateDeviceRGB();

        if(!colorSpace) {
            NSLog(@"Error allocating color space RGB\n");
            return NULL;
        }

        // Allocate memory for image data
        bitmapData = (uint32_t *)malloc(bufferLength);

        if(!bitmapData) {
            NSLog(@"Error allocating memory for bitmap\n");
            CGColorSpaceRelease(colorSpace);
            return NULL;
        }

        //Create bitmap context
        context = CGBitmapContextCreate( bitmapData, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast|kCGBitmapByteOrder32Big );

        if( !context )
        {
            free( bitmapData );
            NSLog( @"Bitmap context not created" );
        }

        CGColorSpaceRelease( colorSpace );

        return context;
    }


    - (UIImage*) convertBitmapRGBA8ToUIImage:(unsigned char*) pBuffer withWidth:(int) nWidth withHeight:(int) nHeight
    {
        // Create the bitmap context
        const size_t nColorChannels = 4;
        const size_t nBitsPerChannel = 8;
        const size_t nBytesPerRow = ((nBitsPerChannel * nWidth) / 8) * nColorChannels;

        CGColorSpaceRef oCGColorSpaceRef = CGColorSpaceCreateDeviceRGB();
        CGContextRef oCGContextRef = CGBitmapContextCreate( pBuffer, nWidth, nHeight, nBitsPerChannel, nBytesPerRow ,  oCGColorSpaceRef, kCGImageAlphaPremultipliedLast|kCGBitmapByteOrder32Big );

        // create the image:
        CGImageRef toCGImage = CGBitmapContextCreateImage(oCGContextRef);
        UIImage* pImage = [[UIImage alloc] initWithCGImage:toCGImage];

        return pImage;
    }

Ответы [ 2 ]

0 голосов
/ 26 июля 2019

Храните данные изображения напрямую, не используйте UIImage.pngData () для преобразования изображения в данные, этот метод изменит значение rgb пикселя, если у пикселя есть альфа-канал.

0 голосов
/ 04 апреля 2019

Судя по исходному коду, вы используете данные BGRA (RGB + альфа-канал), импортированные из исходных изображений PNG. Когда вы присоединяете изображения к проекту iOS, Xcode будет предварительно обрабатывать каждое изображение, чтобы предварительно умножить данные канала RGB и A по соображениям производительности. Таким образом, к моменту загрузки изображения на устройство iPhone значения RGB для непрозрачных (не A = 255) пикселей могут быть изменены. Номера RGB изменены, но фактические данные изображения будут отображаться одинаково при отображении на экране iOS. Это известно как «прямая альфа» против «предварительно умноженной альфы».

...