居中 fill/fit Shader Metal 中的 2D 纹理

Centered fill/fit 2D texture in Shader Metal

在视图中调整 fit/fill 纹理时,我需要将其置于 2D 纹理的中心,但我无法配置 uv 坐标。

Original image

When adjust fill, show the first part of the image not the center:

Fill image

when adjust fit, not get the correct center:

Fit image

float2 adjustPos(float2 size,
                 float2 uv) {
    uv.x /= size.x;
    uv.y /= size.y;
    uv.y = 1.0f - uv.y;
    
    return uv;
}

float2 scaleTexture(texture2d<float, access::sample> tex2d,
                    float2 size,
                    float2 uv,
                    int mode) {
    int width = tex2d.get_width();
    int height = tex2d.get_height();
    float widthRatio  = size.x/width;
    float heightRatio = size.y/height;
    float2 pos;
    
    if (mode == 0) { // Aspect Fit
        int2 newSize = int2(width*widthRatio, height*widthRatio);
        pos = adjustPos(float2(newSize), uv);
        float y = (uv.y/size.y) / 2.0;
        y = y-pos.y;
        y = 1.0f-y;
        pos.y = y;
    } else if (mode == 1) { // Aspect Fill
        int2 newSize = int2(width*heightRatio, height*heightRatio);
        pos = adjustPos(float2(newSize), uv);
        
        if (newSize.x != size.x) {
            pos.x  = 0.5f + ((pos.x - 0.5f) * (1.0f - (heightRatio/100)));
        }
    } else {
        float scale = min(widthRatio, heightRatio);
        int2 newSize = int2(width*scale, height*scale);
        pos = adjustPos(float2(newSize), uv);
    }
    
    return pos;
}

您可以使用此着色器并根据您的要求对其进行自定义。此解决方案可让您在给定尺寸内进行纹理填充。

以下金属着色器采用纹理、输出大小、预期内容模式和 returns 给定大小的 fit/fill 图像。

fragment float4 fragment_aspect_fitfill(
                               VertexOut vertexIn [[stage_in]],
                               texture2d<float, access::sample> sourceTexture [[texture(0)]],
                               sampler sourceSampler [[sampler(0)]],
                               constant float2 &size [[ buffer(0) ]],
                               constant float &contentMode [[ buffer(1) ]])
{
    float2 uv = vertexIn.textureCoordinate;
    
    //Calculate Aspect Ration for both Texture and Expected output texture
    float textureAspect = (float)sourceTexture.get_width() / (float)sourceTexture.get_height();
    float frameAspect = (float)size.x / (float)size.y;
    
    
    float scaleX = 1, scaleY = 1;
    float textureFrameRatio = textureAspect / frameAspect;
    bool portraitTexture = textureAspect < 1;
    bool portraitFrame = frameAspect < 1;
    
    
    // Content mode 0 is for aspect Fill, 1 is for Aspect Fit
    if(contentMode == 0.0) {
        if(portraitFrame)
            scaleX = 1.f / textureFrameRatio;
        else
            scaleY = textureFrameRatio;
    } else if(contentMode == 1.0) {
        if(portraitFrame)
            scaleY = textureFrameRatio;
        else
            scaleX = 1.f / textureFrameRatio;
    }
    
    float2 textureScale = float2(scaleX, scaleY);
    float2 vTexCoordinate = textureScale * (uv - 0.5) + 0.5;
    

    return sourceTexture.sample(sourceSampler, vTexCoordinate);
}

*提示:本MSL使用了MetalPetal中的struct部分。