You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS及Android Oreo下OpenGL宽色域渲染与Delphi多纹理适配问题

针对你用Delphi开发、需要在iOS和Android Oreo上通过OpenGL处理宽色域图像并实现同一窗体混合渲染sRGB与宽色域纹理的问题,我整理了可落地的实现思路和代码示例:

一、先完成平台级的宽色域渲染配置

不管是iOS还是Android Oreo,首先要确保OpenGL渲染表面(窗体/控件)启用宽色域支持,否则后续的纹理处理都会白搭。

iOS平台配置

iOS从iOS 9开始支持Display P3宽色域,Delphi的窗体对应iOS的UIView,我们需要修改其layer的颜色空间:

uses iOSapi.UIKit, iOSapi.CoreGraphics, Macapi.ObjectiveC;

procedure EnableiOSWideGamut(const AForm: TForm);
var
  LView: UIView;
  LColorSpace: CGColorSpaceRef;
begin
  LView := UIView(AForm.Handle);
  // 创建Display P3颜色空间
  LColorSpace := CGColorSpaceCreateWithName(kCGColorSpaceDisplayP3);
  if LColorSpace <> nil then
  begin
    // 设置layer的颜色空间和屏幕缩放比例
    LView.layer.setContentsScale(UIScreen.mainScreen.scale);
    LView.layer.setColorSpace(LColorSpace);
    CGColorSpaceRelease(LColorSpace);
    // 确保OpenGL上下文使用对应的颜色空间
    glEnable(GL_FRAMEBUFFER_SRGB_EXT);
  end;
end;

Android Oreo平台配置

Android 8.0(Oreo)引入了对宽色域的官方支持,需要通过EGL配置来启用Display P3:

uses Androidapi.EGL, Androidapi.GLES20, Androidapi.JNI.GraphicsContentViewText;

procedure EnableAndroidWideGamut(const AGLSurfaceView: TGLSurfaceView);
var
  LDisplay: EGLDisplay;
  LConfigAttribs: array[0..6] of EGLint;
  LConfig: EGLConfig;
  LNumConfigs: EGLint;
begin
  LDisplay := eglGetDisplay(EGL_DEFAULT_DISPLAY);
  eglInitialize(LDisplay, nil, nil);

  // 定义EGL配置属性,要求8位RGB通道
  LConfigAttribs[0] := EGL_RED_SIZE;
  LConfigAttribs[1] := 8;
  LConfigAttribs[2] := EGL_GREEN_SIZE;
  LConfigAttribs[3] := 8;
  LConfigAttribs[4] := EGL_BLUE_SIZE;
  LConfigAttribs[5] := 8;
  LConfigAttribs[6] := EGL_NONE;

  // 选择支持的配置并设置宽色域
  if eglChooseConfig(LDisplay, @LConfigAttribs[0], @LConfig, 1, @LNumConfigs) and (LNumConfigs > 0) then
  begin
    // 检查是否支持Display P3扩展
    if eglQueryString(LDisplay, EGL_EXTENSIONS).Contains('EGL_EXT_color_display_p3') then
    begin
      eglSurfaceAttrib(LDisplay, AGLSurfaceView.EGLSurface, EGL_COLORSPACE_DISPLAY_P3_EXT, EGL_TRUE);
    end;
  end;
end;
二、加载带嵌入色域配置文件的PNG/JPEG纹理

Delphi的TBitmap支持读取图像嵌入的ICC配置文件,我们可以利用这一点区分sRGB和宽色域纹理,再对应设置OpenGL纹理格式:

type
  TColorSpace = (csSRGB, csDisplayP3, csAdobeRGB);

var
  TextureColorSpaceMap: TDictionary<GLuint, TColorSpace>;

procedure LoadTextureWithColorSpace(const AFileName: string; out ATextureID: GLuint);
var
  LBitmap: TBitmap;
  LICCStream: TMemoryStream;
  LColorSpace: TColorSpace;
begin
  LBitmap := TBitmap.Create;
  LICCStream := TMemoryStream.Create;
  try
    LBitmap.LoadFromFile(AFileName);
    LColorSpace := csSRGB; // 默认sRGB

    // 提取并解析ICC配置文件
    if LBitmap.ICCProfile.SaveToStream(LICCStream) then
    begin
      LICCStream.Position := 0;
      // 这里需要你实现ICC配置文件的解析逻辑,判断是Display P3/Adobe RGB还是sRGB
      // 示例:简单判断ICC名称(实际需要更严谨的解析)
      if ParseICCProfileName(LICCStream) = 'Display P3' then
        LColorSpace := csDisplayP3
      else if ParseICCProfileName(LICCStream) = 'Adobe RGB (1998)' then
        LColorSpace := csAdobeRGB;
    end;

    // 创建OpenGL纹理
    glGenTextures(1, @ATextureID);
    glBindTexture(GL_TEXTURE_2D, ATextureID);

    if LColorSpace = csSRGB then
    begin
      // sRGB纹理直接使用GL_SRGB8_ALPHA8格式,OpenGL会自动转换到线性空间
      glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, LBitmap.Width, LBitmap.Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, LBitmap.ScanLine[LBitmap.Height-1]);
    end
    else
    begin
      // 宽色域纹理使用GL_RGBA8格式,后续在着色器中手动转换颜色空间
      glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, LBitmap.Width, LBitmap.Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, LBitmap.ScanLine[LBitmap.Height-1]);
      TextureColorSpaceMap.Add(ATextureID, LColorSpace);
    end;

    // 设置纹理过滤参数
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  finally
    LICCStream.Free;
    LBitmap.Free;
  end;
end;

// 辅助函数:解析ICC配置文件的名称(示例实现,实际需用专业库解析)
function ParseICCProfileName(const AStream: TMemoryStream): string;
begin
  // 这里省略具体的ICC解析逻辑,你可以使用第三方库或手动解析ICC头信息
  // 返回配置文件的名称,比如"Display P3"
  Result := 'Display P3';
end;
三、同一渲染表面混合渲染的核心:着色器中的颜色空间转换

要混合sRGB和宽色域纹理,关键是把所有纹理的颜色转换到线性颜色空间进行渲染,最后再由OpenGL转换到显示的宽色域空间。下面是对应的GLSL着色器示例:

顶点着色器

#version 300 es
precision mediump float;

layout(location = 0) in vec3 aPosition;
layout(location = 1) in vec2 aTexCoord;

out vec2 vTexCoord;

void main() {
    gl_Position = vec4(aPosition, 1.0);
    vTexCoord = aTexCoord;
}

片段着色器

#version 300 es
precision mediump float;

in vec2 vTexCoord;
uniform sampler2D uTexture;
uniform int uColorSpace; // 0: sRGB, 1: Display P3, 2: Adobe RGB

out vec4 FragColor;

// sRGB转线性空间
vec3 SRGBToLinear(vec3 color) {
    return mix(pow(color / 12.92, vec3(2.4)), pow((color + 0.055) / 1.055, vec3(2.4)), step(0.04045, color));
}

// Display P3转线性空间(Display P3的gamma约为2.2)
vec3 P3ToLinear(vec3 color) {
    return pow(color, vec3(2.2));
}

// Adobe RGB转线性空间
vec3 AdobeRGBToLinear(vec3 color) {
    return pow(color, vec3(2.19921875));
}

void main() {
    vec4 texColor = texture(uTexture, vTexCoord);
    vec3 linearColor;

    // 根据纹理的颜色空间转换到线性空间
    if (uColorSpace == 0) {
        linearColor = SRGBToLinear(texColor.rgb);
    } else if (uColorSpace == 1) {
        linearColor = P3ToLinear(texColor.rgb);
    } else if (uColorSpace == 2) {
        linearColor = AdobeRGBToLinear(texColor.rgb);
    } else {
        linearColor = texColor.rgb;
    }

    // 输出线性颜色,OpenGL会自动转换到显示的宽色域空间
    FragColor = vec4(linearColor, texColor.a);
}

Delphi中渲染时的着色器参数传递

在渲染每个纹理前,需要把对应的颜色空间值传递给着色器的uColorSpace uniform变量:

procedure RenderTexture(const ATextureID: GLuint; const AShaderProgram: GLuint);
var
  LColorSpaceLoc: GLint;
  LColorSpace: TColorSpace;
begin
  glBindTexture(GL_TEXTURE_2D, ATextureID);
  LColorSpaceLoc := glGetUniformLocation(AShaderProgram, 'uColorSpace');

  if TextureColorSpaceMap.TryGetValue(ATextureID, LColorSpace) then
  begin
    case LColorSpace of
      csSRGB: glUniform1i(LColorSpaceLoc, 0);
      csDisplayP3: glUniform1i(LColorSpaceLoc, 1);
      csAdobeRGB: glUniform1i(LColorSpaceLoc, 2);
    end;
  end
  else
  begin
    // 默认sRGB
    glUniform1i(LColorSpaceLoc, 0);
  end;

  // 执行绘制逻辑(绑定顶点数据、绘制三角形等)
  // ...
end;
四、注意事项
  • 设备兼容性:iOS需iPhone 7及以后机型,Android Oreo设备需硬件支持Display P3色域;
  • OpenGL版本:建议使用OpenGL ES 3.0及以上,确保支持GL_SRGB8_ALPHA8和相关颜色空间扩展;
  • ICC解析:实际项目中建议使用成熟的ICC解析库(比如Delphi的第三方组件),避免手动解析的误差;
  • 性能:颜色空间转换在着色器中执行,对性能影响很小,无需担心。

内容的提问来源于stack exchange,提问作者zeus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 07:41:35