ios 如何在保留纵横比的情况下调整CVPixelBufferRef 420f的大小?

lymnna71  于 2023-05-02  发布在  iOS
关注(0)|答案(1)|浏览(275)

我正在尝试将kCVPixelFormatType_420YpCbCr8BiPlanarFullRange(420f)像素格式的像素缓冲区调整为另一个大小,同时保留纵横比并添加黑条(如果需要)。
我使用Accelerate框架中的vImageScale_Planar8和vImageRotate90_Planar8函数缩放和旋转Y平面,使用vImageScale_CbCr8和vImageRotate90_Planar16U函数缩放和旋转CbCr平面。
代码如下:

+ (CVPixelBufferRef)resizeProportionallyBuffer:(CVPixelBufferRef)srcPixelBuffer withOrientation:(CGImagePropertyOrientation)orientation toSize:(CGSize)dstSize {
    OSType pixelFormat = CVPixelBufferGetPixelFormatType(srcPixelBuffer);
    
    CVPixelBufferLockFlags srcFlags = kCVPixelBufferLock_ReadOnly;
    CVPixelBufferLockBaseAddress(srcPixelBuffer, srcFlags);
    
    void *srcLuminanceData = CVPixelBufferGetBaseAddressOfPlane(srcPixelBuffer, 0);
    size_t srcLuminanceWidth = CVPixelBufferGetWidthOfPlane(srcPixelBuffer, 0);//750
    size_t srcLuminanceHeight = CVPixelBufferGetHeightOfPlane(srcPixelBuffer, 0);//1334
    size_t srcLuminanceBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(srcPixelBuffer, 0);//768
    
    void *srcCbCrData = CVPixelBufferGetBaseAddressOfPlane(srcPixelBuffer, 1);
    int srcCbCrWidth = (int)CVPixelBufferGetWidthOfPlane(srcPixelBuffer, 1);//375
    int srcCbCrHeight = (int)CVPixelBufferGetHeightOfPlane(srcPixelBuffer, 1);//667
    size_t srcCbCrBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(srcPixelBuffer, 1);//768
    
    vImage_Buffer srcLuminanceBuffer = {
        .data = srcLuminanceData,
        .width = srcLuminanceWidth,
        .height = srcLuminanceHeight,
        .rowBytes = srcLuminanceBytesPerRow
    };
    
    vImage_Buffer srcCbCrBuffer = {
        .data = srcCbCrData,
        .width = srcCbCrWidth,
        .height = srcCbCrHeight,
        .rowBytes = srcCbCrBytesPerRow
    };
    
    size_t srcWidth = srcLuminanceWidth;
    size_t srcHeight = srcLuminanceHeight;
    int dstWidth = (int)dstSize.width;
    int dstHeight = (int)dstSize.height;
    uint8_t rotationConstant = 0;
    size_t scaledWidth = srcLuminanceWidth;
    size_t scaledHeight = srcLuminanceHeight;
    if (orientation == kCGImagePropertyOrientationUp || orientation == kCGImagePropertyOrientationDown) {
        BOOL srcIsWider = dstHeight * srcWidth > dstWidth * srcHeight;
        scaledWidth = srcIsWider ? dstWidth : (size_t)round((CGFloat)(dstHeight * srcWidth) / (CGFloat)srcHeight);
        scaledHeight = srcIsWider ? (size_t)round((CGFloat)(dstWidth * srcHeight) / (CGFloat)srcWidth) : dstHeight;
        rotationConstant = (orientation == kCGImagePropertyOrientationUp) ? kRotate0DegreesClockwise : kRotate180DegreesClockwise;
    } else if (orientation == kCGImagePropertyOrientationLeft || orientation == kCGImagePropertyOrientationRight) {
        BOOL srcIsWider = dstHeight * srcHeight > dstWidth * srcWidth;
        scaledHeight = srcIsWider ? dstWidth: (size_t)round((CGFloat)(srcHeight * dstHeight) / (CGFloat)srcWidth);
        scaledWidth = srcIsWider ? (size_t)round((CGFloat)(srcWidth * dstWidth) / (CGFloat)srcHeight) : dstHeight;
        rotationConstant = (orientation == kCGImagePropertyOrientationLeft) ? kRotate90DegreesClockwise : kRotate90DegreesCounterClockwise;
    }
    
    Pixel_8 backColor = 0;
    
    CVPixelBufferRef dstPixelBuffer = NULL;
    CVReturn result = CVPixelBufferCreate(kCFAllocatorDefault,
                                          dstWidth,
                                          dstHeight,
                                          pixelFormat,
                                          nil,
                                          &dstPixelBuffer);
    CVPixelBufferLockBaseAddress(dstPixelBuffer, 0);
    
    void *dstLuminanceData = CVPixelBufferGetBaseAddressOfPlane(dstPixelBuffer, 0);
    int dstLuminanceWidth = (int)CVPixelBufferGetWidthOfPlane(dstPixelBuffer, 0);//1280
    int dstLuminanceHeight = (int)CVPixelBufferGetHeightOfPlane(dstPixelBuffer, 0);//720
    size_t dstLuminanceBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(dstPixelBuffer, 0);//1280
    
    void *dstCbCrData = CVPixelBufferGetBaseAddressOfPlane(dstPixelBuffer, 1);
    int dstCbCrWidth = (int)CVPixelBufferGetWidthOfPlane(dstPixelBuffer, 1);//640
    int dstCbCrHeight = (int)CVPixelBufferGetHeightOfPlane(dstPixelBuffer, 1);//360
    size_t dstCbCrBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(dstPixelBuffer, 1);//1280
    
    vImage_Buffer dstLuminanceBuffer = {
        .data = dstLuminanceData,
        .width = dstLuminanceWidth,
        .height = dstLuminanceHeight,
        .rowBytes = dstLuminanceBytesPerRow
    };
    
    vImage_Buffer dstCbCrBuffer = {
        .data = dstCbCrData,
        .width = dstCbCrWidth,
        .height = dstCbCrHeight,
        .rowBytes = dstCbCrBytesPerRow
    };
    
    void *dstLuminanceIntermediateBufferData = malloc(scaledWidth*scaledHeight);
    vImage_Buffer dstLuminanceIntermediateBuffer = {
        .data = dstLuminanceIntermediateBufferData,
        .width = scaledWidth,
        .height = scaledHeight,
        .rowBytes = scaledWidth
    };
    vImage_Error error = vImageScale_Planar8(&srcLuminanceBuffer, &dstLuminanceIntermediateBuffer, nil, kvImageNoFlags);
    error = vImageRotate90_Planar8(&dstLuminanceIntermediateBuffer, &dstLuminanceBuffer, rotationConstant, backColor, kvImageBackgroundColorFill);
    free(dstLuminanceIntermediateBufferData);

    void *dstCbCrIntermediateBufferData = malloc(scaledWidth*scaledHeight);
    vImage_Buffer dstCbCrIntermediateBuffer = {
        .data = dstCbCrIntermediateBufferData,
        .width = scaledWidth/2,
        .height = scaledHeight/2,
        .rowBytes = scaledWidth
    };
    error = vImageScale_CbCr8(&srcCbCrBuffer, &dstCbCrIntermediateBuffer, nil, kvImageNoFlags);
    error = vImageRotate90_Planar16U(&dstCbCrIntermediateBuffer, &dstCbCrBuffer, rotationConstant, 127, kvImageBackgroundColorFill);
    free(dstCbCrIntermediateBufferData);
    
    CVPixelBufferUnlockBaseAddress(srcPixelBuffer, srcFlags);
    CVPixelBufferUnlockBaseAddress(dstPixelBuffer, 0);
    
    return dstPixelBuffer;
}

代码可以工作,但是当图像具有kCGImagePropertyOrientationLeft或kCGImagePropertyOrientationRight方向时,会添加绿色条。
如何解决此问题?

yzckvree

yzckvree1#

核心图形和核心视频中的图像都可以具有方向标签。这意味着图像可以在存储容器中相对于它应该如何被绘制而旋转或翻转。然而,VImage没有任何这样的概念。就它而言,像素就是像素。当逻辑高度和宽度与物理高度和宽度不匹配时,就会出现问题。VImage只关心物理高度和宽度,而这些其他API可能会为您提供逻辑高度和宽度,因为它会出现在屏幕上,所以您可以正确定位它。(他们将存储格式视为实现细节,而VImage不这样做。当旋转是向左或向右时,我建议在填充vImage_Buffer结构时尝试交换高度和宽度,以便它匹配存储方向。
如果条纹对于重采样过滤器来说真的很小,比如1-3像素宽,那么我可能也会看看我是否正确地描述了图像的大小。重采样过滤器将查看至少6个最接近新像素中心的src像素。如果其中一些是垃圾,特别是在图像的右侧或底部,那么你会得到垃圾输入/垃圾输出。Vimage非常小心地避免触及不存在或逻辑上不存在的像素,但如果数据在vimage_buffer结构中描述不正确,那么它可能被授权使用该数据,然后您将在边缘和/或崩溃处获得条纹。
请注意,vimage比例不适用于平铺图像。它将需要看到瓷砖外的周围区域才能正确工作,这很可能是不存在的。这将导致拼接边缘在结果中的平铺边界处可见。要解决这个问题,你需要回到较低级别的剪切程序。唉,vImage没有提供一种方法来知道需要多少额外的扫描线/列,所以这对于下采样来说很难。

相关问题