在 iOS 上从两个 jpeg 加载 RGBA 图像 - OpenGL ES 2.0

Posted

技术标签:

【中文标题】在 iOS 上从两个 jpeg 加载 RGBA 图像 - OpenGL ES 2.0【英文标题】:Load an RGBA image from two jpegs on iOS - OpenGL ES 2.0 【发布时间】:2014-03-08 01:37:36 【问题描述】:

基于我的问题here 中的概念和我的另一个问题here 中概述的PNG 文件问题,我将尝试从两个jpeg 加载RGBA 图像。一个将包含 RGB,另一个仅包含 Alpha。我可以将第二个保存为灰度 jpeg 或 RGB,然后从红色组件中提取 alpha 数据。

在第二步中,我会将原始图像数据保存到缓存中的文件中。然后,我将进行测试以确定加载原始数据或解压缩 jpeg 并构建原始数据的速度更快。如果我确定它更快,那么在后续加载时,我可以检查缓存中是否存在原始文件。如果没有,我将跳过该文件保存。

我知道如何将两个 jpeg 加载到两个 UIImage 中。我不确定是将一个 UIImage 中的 rgb 与我用于 alpha 的另一个 UIImage 的任何通道交错的最快或最有效的方法。

我看到了两种可能性。一个是在下面的评论 B 中。遍历所有像素并将红色从“alpha jpeg”复制到 imageData 流的 alpha。

另一个是也许有一些神奇的 UIImage 命令可以将一个频道从一个频道复制到另一个频道。如果我这样做了,那将是评论 A 附近的某个地方。

有什么想法吗?

编辑 - 另外.. 该过程不能破坏任何 RGB 信息。我需要这个过程的全部原因是来自 photoshop 的 PNG 将 rgb 与 alpha 预乘,从而破坏了 rgb 信息。我在自定义 openGL 着色器中将 alpha 用于除 alpha 之外的其他内容。所以我正在寻找可以将 alpha 设置为任何东西的原始 RGBA 数据,以用作高光贴图、照明贴图或高度贴图或除 alpha 之外的其他东西。

这是我的起始代码减去我的错误检查和其他专有废话。我有一组纹理,用于管理有关纹理的所有内容:

if (textureInfo[texIndex].generated==NO) 
    glGenTextures(1, &textureInfo[texIndex].texture);
    textureInfo[texIndex].generated=YES;


glBindTexture(GL_TEXTURE_2D, textureInfo[texIndex].texture);

// glTexParameteri commands are here based on options for this texture

NSString *path = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"%@_RGB",name] ofType:type];

NSData *texData = [[NSData alloc] initWithContentsOfFile:path];
UIImage *imageRGB = [[UIImage alloc] initWithData:texData];

path = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"%@_A",name] ofType:type];

texData = [[NSData alloc] initWithContentsOfFile:path];
UIImage *imageAlpha = [[UIImage alloc] initWithData:texData];

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
void *imageDataRGB = malloc( heightRGB * widthRGB * 4 );
void *imageDataAlpha = malloc( heightA * widthA * 4 );
CGContextRef thisContextRGB = CGBitmapContextCreate( imageDataRGB, widthRGB, heightRGB, 8, 4 * widthRGB, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big );
CGContextRef thisContextA = CGBitmapContextCreate( imageDataAlpha, widthA, heightA, 8, 4 * widthA, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big );

// **** A. In here I want to mix the two.. take the R of imageA and stick it in the Alpha of imageRGB.

CGColorSpaceRelease( colorSpace );
CGContextClearRect( thisContextRGB, CGRectMake( 0, 0, widthRGB, heightRGB ) );
CGContextDrawImage( thisContextRGB, CGRectMake( 0, 0, widthRGB, heightRGB ), imageRGB.CGImage );

// **** B. OR maybe repeat the above 3 lines for the imageA.CGImage and then
// **** It could be done in here by iterating through the data and copying the R byte of imageDataA on to the A byte of the imageDataRGB

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, widthRGB, heightRGB, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageDataRGB);

// **** In here I could save off the merged imageData to a binary file and load that later if it's faster

glBindTexture(GL_TEXTURE_2D, textureInfo[texIndex].texture);

// Generates a full MipMap chain to the current bound texture.
if (useMipmap) 
    glGenerateMipmap(GL_TEXTURE_2D);


CGContextRelease(thisContextRGB);
CGContextRelease(thisContextA);

free(imageDataRGB);
free(imageDataAlpha);

【问题讨论】:

从 RGB 和灰度图像中制作 2 个纹理并使用着色器进行合并可能会更快。您还可以查看 Accelerate.framework。我发现它在 CPU 上工作时有一些有用的 swizzle 例程,它可能有一些东西可以从一个图像中提取一个通道,从另一个图像中提取其他通道。 (我不肯定——只是推测。) 不可能更快。如果你现在看***.com/questions/22110519/…,我正在做 4way 图像。但这需要的 texture2D 调用次数是我实际需要的两倍,因为每次调用都可以获得 4 个浮点数。我只需要 8 个浮点数来渲染一个带有 4 个纹理贴图的片段。尤其是在 iPhone4 等旧设备上,将 texture2D 调用次数减半应该会大大加快渲染时间。 很公平。查看 Accelerate.framework 文档,看起来 vImageSelectChannels_ARGB8888 可以满足您的需求。您甚至可以就地操作,节省一些内存。或者,您可以使用 TIFF 文件存储直接 alpha 并存储 RGBA,而不是 png 文件或多个 jpg,如果这样更容易的话。 这会更容易,但是当我在 Photoshop 中保存 TIFF 时,alpha 显示为灰色。我会乱来看看是否有办法,因为当然不搞砸这一切会更容易。 no.. TIFF 正在预乘 alpha。所以rgb被破坏了。它一定与 Xcode 或 iPhone 正在做的事情有关。因为我可以将 tiff 加载回 PSD 并且所有频道都在那里。或者我上面的代码中的一些东西。 【参考方案1】:

我赞同上面评论中提出的建议,即使用单独的纹理并在着色器中组合。但是,我会解释为什么这可能会更快...

texture2D 调用的数量本身应该与速度没有太大关系。影响速度的重要因素是:(1)需要从CPU复制多少数据到GPU? (您可以轻松测试,在每次调用中使用 N/2 像素调用 texture2D 两次几乎与使用 N 像素在其他条件相同的情况下调用一次一样快),以及(2)实现是否需要重新排列 CPU 内存中的数据在 texture2D 之前(如果是,那么调用可能会非常慢)。有些纹理格式需要重新排列,有些则不需要;通常至少 RGBA、RGB565 和 YUV420 或 YUYV 的一些变体不需要重新排列。步幅和宽度/高度是 2 的幂也可能很重要。

我认为,如果不需要重新排列数据,使用 RGB 调用一次,使用 A 调用一次将与使用 RGBA 调用大约一样快。

由于重新排列比复制慢得多,因此复制 RGBX(忽略第四个通道)然后复制 A 可能会更快,而不是在 CPU 上重新排列 RGB 和 A 然后复制。

附: “在第二步中,我会将原始图像数据保存到缓存中的文件中。然后我将进行测试以确定加载原始数据或解压缩 jpeg 并构建原始数据的速度更快。” - 从内存以外的任何地方读取原始数据可能比解压缩慢很多。 1 兆像素图像从 jpeg 解压缩需要几十毫秒,或者从闪存驱动器读取原始数据需要数百毫秒。

【讨论】:

我同意你的第二点,即加载 raw 可能会更慢,并且会跳过该步骤。但是我要测试我的固定 rgba 是否有 2 个 texture2d 调用比我调用 texture2d 4 次的旧方法更快。正如我已经回答了我自己关于如何组合 2 个 jpeg 的问题。我在您的回答中不明白的是关于“每次通话中的 n/2 像素”的部分。我的纹理映射的大小没有改变。每个片段仍然有相同数量的像素。只是在我无法获得干净的 rgb 之前,如果我包含另一个编码为 alpha 的地图。也许你可以解释一下。 除非您说要创建一个仅亮度纹理作为我的另一个纹理并调用一个仅获取 rgb 而另一个仅获取 alpha。但根据 openGL ES 2.0 参考,所有 texture2d 调用都返回一个 vec4 - 所以我看不出这会更好。 @badweasel:“除非你说要创建一个仅亮度纹理作为我的另一个纹理,并调用一个只获取 rgb 而另一个只获取 alpha。” - 对,就是那样。将仅亮度纹理从 CPU 复制到 GPU 通常比 RGBA 快 4 倍,因为数据量是 RGBA 的 1/4。当然,假设没有重新排列。 @badweasel:“所有 texture2d 调用都返回一个 vec4” - 是的,但这实际上并不是幕后发生的事情。如果您访问仅亮度的纹理,请确保您获得一个可用作 vec4 的值,但其中的最后 3 个元素只是零常数,它们不会从纹理中读取。如果您只对第一个元素进行数学运算,而从不引用其余元素,则根本没有性能损失。 @badweasel:很难说;这将取决于硬件。我不会太担心使用 4 个纹理,这似乎是 OpenGL 无论如何都希望你做的(当然,只制作 alpha 纹理亮度)。性能差异很大的一种情况是,如果您的单个 4 向纹理有一些区域您只需要 alpha,但您正在读取所有 4 个通道;那会慢得多。在您的其他问题中,我更喜欢方法 1。【参考方案2】:

这是一个非常简单的 alpha 复制到具有干净 rgb 的版本。尽管关于我的着色器是否更快地每个片段调用 texture2D 2 或 4 次存在争议,但下面的方法可以将未预乘的 RGBA 放入我的glTexImage2D(GL_TEXTURE_2D...

这是我的全部方法:

-(BOOL)quickLoadTexPartsToNumber:(int)texIndex imageNameBase:(NSString *)name ofType:(NSString *)type  flipImage:(bool)flipImage clamp:(bool)clamp  mipmap:(bool)useMipmap

    //NSLog(@"loading image: %@ into %i",name, texIndex);

    // generate a new texture for that index number..  if it hasn't already been done
    if (textureInfo[texIndex].generated==NO) 
        glGenTextures(1, &textureInfo[texIndex].texture);
        textureInfo[texIndex].generated=YES;
    

    glBindTexture(GL_TEXTURE_2D, textureInfo[texIndex].texture);

    if (useMipmap) 
        if (clamp) 
            glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);
            glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR_MIPMAP_NEAREST);

            glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
            glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
        

        if (hardwareLimitions==HARDWARE_LIMITS_NONE) 
            glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
            glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR_MIPMAP_LINEAR);
        
        else 
            glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST_MIPMAP_LINEAR);
            glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST_MIPMAP_LINEAR);
        
    
    else 
        if (clamp) 
            glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
            glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);

            glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
            glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
        

        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    

    NSString *path = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"%@_RGB",name] ofType:type];

    NSData *texData = [[NSData alloc] initWithContentsOfFile:path];
    UIImage *imageRGB = [[UIImage alloc] initWithData:texData];

    path = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"%@_A",name] ofType:type];

    texData = [[NSData alloc] initWithContentsOfFile:path];
    UIImage *imageAlpha = [[UIImage alloc] initWithData:texData];


    if (imageRGB == nil) 
        NSLog(@"************* Image %@ is nil - there's a problem", [NSString stringWithFormat:@"%@_RGB",name]);
        return NO;
    
    if (imageAlpha == nil) 
        NSLog(@"************* Image %@ is nil - there's a problem", [NSString stringWithFormat:@"%@_A",name]);
        return NO;
    


    GLuint widthRGB = CGImageGetWidth(imageRGB.CGImage);
    GLuint heightRGB = CGImageGetHeight(imageRGB.CGImage);

    GLuint widthAlpha = CGImageGetWidth(imageAlpha.CGImage);
    GLuint heightAlpha = CGImageGetHeight(imageAlpha.CGImage);

    if (widthRGB != widthAlpha || heightRGB!=heightAlpha) 
        NSLog(@"************* Image %@ - RBG and Alpha sizes don't match", name);
    

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    // unsigned char is an 8 bit unsigned integer
    unsigned char *imageDataRGB = malloc( heightRGB * widthRGB * 4 );
    unsigned char *imageDataAlpha = malloc( heightAlpha * widthAlpha * 4 );
    CGContextRef thisContextRGB = CGBitmapContextCreate( imageDataRGB, widthRGB, heightRGB, 8, 4 * widthRGB, colorSpace, kCGImageAlphaNoneSkipLast | kCGBitmapByteOrder32Big );
    CGContextRef thisContextAlpha = CGBitmapContextCreate( imageDataAlpha, widthAlpha, heightAlpha, 8, 4 * widthAlpha, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big );

    if (flipImage)
    
        // Flip the Y-axis  - don't want to do this because in this game I made all my vertex squares upside down.
        CGContextTranslateCTM (thisContextRGB, 0, heightRGB);
        CGContextScaleCTM (thisContextRGB, 1.0, -1.0);

        CGContextTranslateCTM (thisContextAlpha, 0, heightAlpha);
        CGContextScaleCTM (thisContextAlpha, 1.0, -1.0);
    

    CGColorSpaceRelease( colorSpace );
    CGContextClearRect( thisContextRGB, CGRectMake( 0, 0, widthRGB, heightRGB ) );
    // draw the RGB version and skip the alpha
    CGContextDrawImage( thisContextRGB, CGRectMake( 0, 0, widthRGB, heightRGB ), imageRGB.CGImage );

    CGColorSpaceRelease( colorSpace );
    CGContextClearRect( thisContextAlpha, CGRectMake( 0, 0, widthAlpha, heightAlpha ) );
    CGContextDrawImage( thisContextAlpha, CGRectMake( 0, 0, widthAlpha, heightAlpha ), imageAlpha.CGImage );

    int count = 4 * widthRGB * heightRGB;
    for(int i=0; i < count; i+=4)
    
        // copying the alpha (one byte) on to a non-premultiplied rgb is faster than copying the rgb over (3 bytes)
       imageDataRGB[i+3] = imageDataAlpha[i];
    

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, widthRGB, heightRGB, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageDataRGB);

    glBindTexture(GL_TEXTURE_2D, textureInfo[texIndex].texture);

    // Generates a full MipMap chain to the current bound texture.
    if (useMipmap) 
        glGenerateMipmap(GL_TEXTURE_2D);
    

    CGContextRelease(thisContextRGB);
    CGContextRelease(thisContextAlpha);

    free(imageDataRGB);
    free(imageDataAlpha);

    return YES;

我确实尝试过使用 TIF 而不是 PNG,但无论我在过程中的某个地方尝试了什么,rgb 都会与 alpha 进行预乘,从而破坏了 rgb。

这种方法可能被认为是丑陋的,但它对我来说适用于许多层面,并且是我能够将完整的 RGBA8888 未预乘图像导入 OpenGL 的唯一方法。

【讨论】:

这是我一直在寻找的答案。这回答了如何做到这一点的实际问题。是否应该这样做是另一个问题,我投票赞成 Alex。

以上是关于在 iOS 上从两个 jpeg 加载 RGBA 图像 - OpenGL ES 2.0的主要内容,如果未能解决你的问题,请参考以下文章

在 UIButtons 上从服务器加载多个图像?

Python pillow模块出现cannot write mode RGBA as JPEG问题的解决方法

如何在 iOS 上从 UITouch 找到最近的车站点

为啥我在 iOS 上从 Swift 调用服务时收到 SSL 错误?

IOS图片压缩(PNG压缩 JPEG压缩 非UIImageJPEGRepresentation)

渐进式加载-基础讲解