Stride Gambar

Saat gambar video disimpan dalam memori, buffer memori mungkin berisi byte padding tambahan setelah setiap baris piksel. Byte padding memengaruhi bagaimana gambar disimpan dalam memori, tetapi tidak memengaruhi bagaimana gambar ditampilkan.

Langkahnya adalah jumlah byte dari satu baris piksel dalam memori ke baris piksel berikutnya dalam memori. Stride juga disebut pitch. Jika byte padding ada, langkahnya lebih lebar dari lebar gambar, seperti yang ditunjukkan pada ilustrasi berikut.

diagram showing an image plus padding.

Dua buffer yang berisi bingkai video dengan dimensi yang sama dapat memiliki dua stride yang berbeda. Jika Anda memproses gambar video, Anda harus memperhitungkan langkah-langkahnya.

Selain itu, ada dua cara agar gambar dapat diatur dalam memori. Dalam gambar atas ke bawah, baris atas piksel dalam gambar muncul terlebih dahulu dalam memori. Dalam gambar bawah ke atas, baris terakhir piksel muncul terlebih dahulu dalam memori. Ilustrasi berikut menunjukkan perbedaan antara gambar atas ke bawah dan gambar bawah ke atas.

diagram showing top-down and bottom-up images.

Gambar bawah ke atas memiliki langkah negatif, karena langkah didefinisikan sebagai jumlah byte perlu memindahkan ke bawah baris piksel, relatif terhadap gambar yang ditampilkan. Gambar YUV harus selalu di atas ke bawah, dan gambar apa pun yang terkandung dalam permukaan Direct3D harus di atas ke bawah. Gambar RGB dalam memori sistem biasanya di bawah ke atas.

Transformasi video khususnya perlu menangani buffer dengan langkah yang tidak cocok, karena buffer input mungkin tidak cocok dengan buffer output. Misalnya, Anda ingin mengonversi gambar sumber dan menulis hasilnya ke gambar tujuan. Asumsikan bahwa kedua gambar memiliki lebar dan tinggi yang sama, tetapi mungkin tidak memiliki format piksel yang sama atau langkah gambar yang sama.

Contoh kode berikut menunjukkan pendekatan umum untuk menulis fungsi semacam ini. Ini bukan contoh kerja lengkap, karena mengabstraksi banyak detail spesifik.

void ProcessVideoImage(
    BYTE*       pDestScanLine0,     
    LONG        lDestStride,        
    const BYTE* pSrcScanLine0,      
    LONG        lSrcStride,         
    DWORD       dwWidthInPixels,     
    DWORD       dwHeightInPixels
    )
{
    for (DWORD y = 0; y < dwHeightInPixels; y++)
    {
        SOURCE_PIXEL_TYPE *pSrcPixel = (SOURCE_PIXEL_TYPE*)pSrcScanLine0;
        DEST_PIXEL_TYPE *pDestPixel = (DEST_PIXEL_TYPE*)pDestScanLine0;

        for (DWORD x = 0; x < dwWidthInPixels; x +=2)
        {
            pDestPixel[x] = TransformPixelValue(pSrcPixel[x]);
        }
        pDestScanLine0 += lDestStride;
        pSrcScanLine0 += lSrcStride;
    }
}

Fungsi ini mengambil enam parameter:

  • Penunjuk ke awal baris pemindaian 0 di gambar tujuan.
  • Langkah gambar tujuan.
  • Penunjuk ke awal baris pemindaian 0 di gambar sumber.
  • Langkah gambar sumber.
  • Lebar gambar dalam piksel.
  • Tinggi gambar dalam piksel.

Ide umumnya adalah memproses satu baris pada satu waktu, melakukan iterasi pada setiap piksel dalam baris. Asumsikan bahwa SOURCE_PIXEL_TYPE dan DEST_PIXEL_TYPE adalah struktur yang mewakili tata letak piksel untuk gambar sumber dan tujuan. (Misalnya, RGB 32-bit menggunakan Struktur RGBQUAD . Tidak setiap format piksel memiliki struktur yang telah ditentukan sebelumnya.) Mentransmisikan penunjuk array ke jenis struktur memungkinkan Anda mengakses komponen RGB atau YUV dari setiap piksel. Pada awal setiap baris, fungsi menyimpan penunjuk ke baris. Di akhir baris, ia menaikkan penunjuk dengan lebar langkah gambar, yang memajukan penunjuk ke baris berikutnya.

Contoh ini memanggil fungsi hipotetis bernama TransformPixelValue untuk setiap piksel. Ini bisa menjadi fungsi apa pun yang menghitung piksel target dari piksel sumber. Tentu saja, detail yang tepat akan tergantung pada tugas tertentu. Misalnya, jika Anda memiliki format YUV planar, Anda harus mengakses bidang klorma secara independen dari bidang luma; dengan video terjalin, Anda mungkin perlu memproses bidang secara terpisah; dan sebagainya.

Untuk memberikan contoh yang lebih konkret, kode berikut mengonversi gambar RGB 32-bit menjadi gambar AYUV. Piksel RGB diakses menggunakan struktur RGBQUAD, dan piksel AYUV diakses menggunakan struktur DXVA2_AYUVSample8.

//-------------------------------------------------------------------
// Name: RGB32_To_AYUV
// Description: Converts an image from RGB32 to AYUV
//-------------------------------------------------------------------
void RGB32_To_AYUV(
    BYTE*       pDest,
    LONG        lDestStride,
    const BYTE* pSrc,
    LONG        lSrcStride,
    DWORD       dwWidthInPixels,
    DWORD       dwHeightInPixels
    )
{
    for (DWORD y = 0; y < dwHeightInPixels; y++)
    {
        RGBQUAD             *pSrcPixel = (RGBQUAD*)pSrc;
        DXVA2_AYUVSample8   *pDestPixel = (DXVA2_AYUVSample8*)pDest;
        
        for (DWORD x = 0; x < dwWidthInPixels; x++)
        {
            pDestPixel[x].Alpha = 0x80;
            pDestPixel[x].Y = RGBtoY(pSrcPixel[x]);   
            pDestPixel[x].Cb = RGBtoU(pSrcPixel[x]);   
            pDestPixel[x].Cr = RGBtoV(pSrcPixel[x]);   
        }
        pDest += lDestStride;
        pSrc += lSrcStride;
    }
}

Contoh berikutnya mengonversi gambar RGB 32-bit ke gambar YV12. Contoh ini menunjukkan cara menangani format YUV planar. (YV12 adalah format planar 4:2:0.) Dalam contoh ini, fungsi mempertahankan tiga pointer terpisah untuk tiga bidang dalam gambar target. Namun, pendekatan dasarnya sama dengan contoh sebelumnya.

void RGB32_To_YV12(
    BYTE*       pDest,
    LONG        lDestStride,
    const BYTE* pSrc,
    LONG        lSrcStride,
    DWORD       dwWidthInPixels,
    DWORD       dwHeightInPixels
    )
{
    assert(dwWidthInPixels % 2 == 0);
    assert(dwHeightInPixels % 2 == 0);

    const BYTE *pSrcRow = pSrc;
    
    BYTE *pDestY = pDest;

    // Calculate the offsets for the V and U planes.

    // In YV12, each chroma plane has half the stride and half the height  
    // as the Y plane.
    BYTE *pDestV = pDest + (lDestStride * dwHeightInPixels);
    BYTE *pDestU = pDest + 
                   (lDestStride * dwHeightInPixels) + 
                   ((lDestStride * dwHeightInPixels) / 4);

    // Convert the Y plane.
    for (DWORD y = 0; y < dwHeightInPixels; y++)
    {
        RGBQUAD *pSrcPixel = (RGBQUAD*)pSrcRow;
        
        for (DWORD x = 0; x < dwWidthInPixels; x++)
        {
            pDestY[x] = RGBtoY(pSrcPixel[x]);    // Y0
        }
        pDestY += lDestStride;
        pSrcRow += lSrcStride;
    }

    // Convert the V and U planes.

    // YV12 is a 4:2:0 format, so each chroma sample is derived from four 
    // RGB pixels.
    pSrcRow = pSrc;
    for (DWORD y = 0; y < dwHeightInPixels; y += 2)
    {
        RGBQUAD *pSrcPixel = (RGBQUAD*)pSrcRow;
        RGBQUAD *pNextSrcRow = (RGBQUAD*)(pSrcRow + lSrcStride);

        BYTE *pbV = pDestV;
        BYTE *pbU = pDestU;

        for (DWORD x = 0; x < dwWidthInPixels; x += 2)
        {
            // Use a simple average to downsample the chroma.

            *pbV++ = ( RGBtoV(pSrcPixel[x]) +
                       RGBtoV(pSrcPixel[x + 1]) +       
                       RGBtoV(pNextSrcRow[x]) +         
                       RGBtoV(pNextSrcRow[x + 1]) ) / 4;        

            *pbU++ = ( RGBtoU(pSrcPixel[x]) +
                       RGBtoU(pSrcPixel[x + 1]) +       
                       RGBtoU(pNextSrcRow[x]) +         
                       RGBtoU(pNextSrcRow[x + 1]) ) / 4;    
        }
        pDestV += lDestStride / 2;
        pDestU += lDestStride / 2;
        
        // Skip two lines on the source image.
        pSrcRow += (lSrcStride * 2);
    }
}

Dalam semua contoh ini, diasumsikan bahwa aplikasi telah menentukan langkah gambar. Anda terkadang bisa mendapatkan informasi ini dari buffer media. Jika tidak, Anda harus menghitungnya berdasarkan format video. Untuk informasi selengkapnya tentang menghitung langkah gambar dan bekerja dengan buffer media untuk video, lihat Buffer Video Yang Tidak Dikompresi.

Tipe Media Video

Jenis Media