1

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

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

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

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

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

现在,有趣的是,如果我使用我的代码从 PNG 加载数据,并用它创建一个 UIImage(使用下面显示的一些代码),我可以使用该 UIImage 并显示它,复制它,无论如何,如果我检查像素数据——这正是我一开始就给它的(这就是为什么我认为这是苹果修改图像数据的加载保存部分。)

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

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

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;
    }
4

2 回答 2

0

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

于 2019-07-26T07:27:02.357 回答
0

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

于 2019-04-04T19:09:59.103 回答