Apple的图片加载/保存代码似乎在稍微修改我的图片

时间:2018-10-31 23:39:52

标签: ios uiimage uiimagepngrepresentation

我正在编写一些测试以检测无损图像格式的更改(从PNG开始),并发现在Linux和Windows上,图像加载机制按预期工作-但在iOS(未在macOS上尝试过)上如果我从Apple的磁盘上的PNG文件加载或使用Apple的方法保存到磁盘上的PNG文件,则数据总是会稍作更改。

如果我使用任意数量的工具(GIMP / Paint.NET /其他)创建PNG,并且使用跨平台PNG读取代码来检查生成的加载数据的每个像素-它与我在工具中所做的完全匹配(或使用我的跨平台PNG编写代码以编程方式生成。)随后重新加载到创建工具中,将产生完全相同的RGBA8888组件。

如果我使用苹果的从磁盘加载PNG:

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

...然后检查结果像素是否相似但不相同。我希望像在其他平台上一样,数据是相同的。

现在,有趣的是,如果我使用自己的代码从PNG加载数据,并使用该代码创建UIImage(使用下面显示的一些代码),则可以使用该UIImage进行显示,复制,复制任何内容,如果我检查像素数据-正是我首先提供的(这就是为什么我认为这是Apple修改图像数据的节省负载的部分。)

当我指示它保存我认为是具有完美像素数据的好的UIImage,然后用我的PNG加载代码加载该Apple保存的图像时,我可以看到它不是完全相同的数据。苹果使用了几种建议将UIImage保存到PNG(主要是UIImagePNGRepresentation)的方法。

我唯一能想到的是,Apple在iOS上加载或保存时并不真正支持RGBA8888,并且正在对alpha通道进行某种预乘-我对此进行了推测,因为当我第一次开始使用代码时我张贴在我选择的下方

kCGImageAlphaLast

...而不是我最终不得不使用的

kCGImageAlphaPremultipliedLast

因为某些原因,iOS不支持前者。

有人在iOS上有解决此问题的经验吗?

干杯!

我用于将RGBA8888数据推入/拉出UIImage的代码如下:

    - (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 :(得分:0)

根据您的源代码,您似乎正在使用从PNG源图像导入的BGRA(RGB + alpha通道)数据。将图像附加到iOS项目时,出于性能原因,Xcode将对每个图像进行预处理以将RGB和A通道数据预乘。因此,在将图像加载到iPhone设备上时,可以更改非不透明(非A = 255)像素的RGB值。修改了RGB编号,但是当通过iOS渲染到屏幕上时,实际的图像数据将相同。这就是“直接Alpha”与“预乘Alpha”。

答案 1 :(得分:0)

直接存储图像数据,请勿使用UIImage.pngData()将图像转换为数据,如果像素具有alpha通道,此方法将更改像素的rgb值。