Color
YUV  Lab Reports
yvu.gif (989 bytes) YVU-9 and YV-12 to RGB
Planar Format Examples
uyvy.gif (949 bytes)

UYVY to RGB
Packed Format Example

grays.gif (1078 bytes) Y8 Gray Scale
YYYYCbCr 411 to BMP Conversion

yvu.gif (989 bytes) YVU-9 and YV-12 to RGB
ScreenYVUtoRGBDemo.jpg (39415 bytes)

Purpose
The purpose of this program is demonstrate how to read a YV-12 or YVU-9 digital byte stream and display the corresponding RGB color image.

Materials and Equipment

Software Requirements
Windows 95/98
Delphi 3/4/5  (to recompile)
YVUtoRGBDemo.EXE with input files
YVU12.Stream and YVU9.Stream

Hardware Requirements
VGA (or higher) monitor in high color or true color display mode.

Procedure

  1. Double click on the YVUtoRGBDemo.EXE icon to start the program.
  2. Select either the YVU12 or YVU9 image via the radio box at the left.
  3. View the Y, U, V or RGB images via the radio box on the right.
  4. The displayed image is also written to a disk file.
    (WeatherLady.BMP is written for for the YVU-12 selection, while NewsGuy.BMP is written for the YVU-9 selection).

Discussion
YUV color is used in European TV broadcasts.  (YIQ is used in U.S. TV broadcasts.)  The Y component of YUV is not "yellow" but luminance.   Only the Y component of a color TV signal is shown on black-and-white TVs.   The chromaticity information is encoded in the U and V components.

Instead of storing an image as 8 bits of red, 8 bits of green and 8 bits of blue per pixel, an RGB color image can be reduced to 16 bits per pixel by conversion to luminance and color differences:

Luminance, Y = 0.299R + 0.587G + 0.114B
Color Difference (blue), CB (also known as U) = B - Y
Color Difference (red), CR (also known as V) = R - Y

Because the eye is more sensitive to luminance detail than color detail, more bits are used to store the luminance information than the color differences.  In the JPEG compression standard the color differences are sub-sampled to reduce their data content.   The JPEG 4:1:1 scheme means there are four samples of Y to each sample of CB and CR.  See [Awcock96, pp. 260-261] for details.

CB and CR stands for Chrominance Blue and Chrominance Red. Chrominance is the amount of brightness that is perceived by the human eye.  The more light you perceive, the higher the chrominance value

One common YUV format is the planar format, where each component is stored as a separate array, with the final image formed by fusing the three separate planes.  (See the UYVY Lab Report below for a packed format example.)  Two common planar YUV formats are YV-12 and YVU-9.  

Here's a visual description of YV-12 and YVU-9 file formats and the resulting RGB images:

YV-12
Y V U
WeatherLady12Y.jpg (20851 bytes) WeatherLady12V.jpg (4431 bytes) WeatherLady12U.jpg (4767 bytes)
WeatherLady12RGB.jpg (27130 bytes)

 

YVU-9
Y V U
NewsGuy9Y.jpg (18752 bytes) NewsGuy9V.jpg (1737 bytes) NewsGuy9U.jpg (1671 bytes)
NewsGuy9RGB.jpg (24254 bytes)

Understanding the format of a digital video stream is essential in processing an image.   Determining the X and Y dimensions of an image in a file is the first step.  In this case, I was given the size of each image.

CONST
  YRowCount      = 288;
  YColumnCount = 352;

The "Y" prefix in these constants doesn't refer to the "Y" spatial dimension but the "Y" luminance data.  (Too many Ys here!)

Common  processing for YVU-9 and YV-12 images is possible by introducing a "Factor" parameter.  This information is selected via the RadioGroup129 control:

Format Factor Input File Output File
YV-12 2 YVU12.Stream WeatherLady.BMP
YVU-9 4 YVU9.Stream NewsGuy.BMP

The YVU12.Stream and YVU9.Stream files contain all the image information.   The "Y" information is first, and is followed by the "V" and "U" information:

File Section Index of First Byte Columns Rows
Y 0 YColumnCount YRowCount
V YColumnCount*YRowCount YColumnCount / Factor YRowCount / Factor
U YColumnCount*YRowCount *
(1 + 1/Factor2)
YColumnCount / Factor YRowCount / Factor

The total file size is YColumnCount*YRowCount * (1 + 2/Factor2), which for the 352-by-288 files is:

File Size of 352-by-288 Image
Format Factor FileSize
YV-12 2 152,064
YVU-9 4 114,048

The Y, V and U information can be read from the same file by opening three file streams and seeking to the position of the first byte of the appropriate section:

StreamY := TFileStream.Create(InFile, fmOpenRead + fmShareDenyNone);
StreamV := TFileStream.Create(InFile, fmOpenRead + fmShareDenyNone);
StreamU := TFileStream.Create(InFile, fmOpenRead + fmShareDenyNone);
...
FilePointer := 0;
StreamY.Seek(FilePointer, soFromBeginning);

FilePointer := YRowCount * YColumnCount;
StreamV.Seek(FilePointer, soFromBeginning);

FilePointer := FilePointer + (YRowCount DIV Factor) * (YColumnCount DIV Factor);
StreamU.Seek(FilePointer, soFromBeginning);

Ignoring palettes is always the easiest approach when working with color images, so a 24-bit "true color" image is created:

Bitmap := TBitmap.Create;
TRY
  Bitmap.Width := YColumnCount;
  Bitmap.Height := YRowCount;
  Bitmap.PixelFormat := pf24Bit;
...

The following shows the mechanics of reading the three streams from the single file for each of the scanlines.  Note the U and V rows are used Factor times, and the pixel information within a scanline is used Factor times. 

FOR j := 0 TO YRowCount-1 DO
BEGIN
  // Read each "row" of data
  StreamY.Read(BufferY, YColumnCount {bytes});

  // YUV12: Read U and V "row" of data on even-numbered rows
  // Re-use the same row of data for the odd-numbered rows
  //
  // YUV9: Read new row every fourth output row. Re-use
  // the same row for the other rows.
  IF       j MOD Factor = 0
  THEN BEGIN
    StreamU.Read(BufferU, YColumnCount DIV Factor {bytes});
    StreamV.Read(BufferV, YColumnCount DIV Factor {bytes});
  END;

  Row := Bitmap.Scanline[j];
  FOR i := 0 TO YColumnCount-1 DO
  BEGIN
    Y := BufferY[i];
    IF       i MOD Factor = 0
    THEN BEGIN
      U := BufferU[i DIV Factor];
      V := BufferV[i DIV Factor]
    END;

    WITH Row[i] DO
    BEGIN

      <convert YUV to RGB for each pixel>

    END;
  END
END;

The "convert YUV to RGB for each pixel" section shown above is a CASE statement in the actual program to select the Y, U, V or RGB images (based on the RadioGroupYVURGB control).  The Y, U, V images are shades of gray since the corresponding values are assigned to each of the  R, G and B pixel components.  For example, the Y image is created in this case:

CASE RadioGroupYVURGB.ItemIndex OF
  // Y plane
  0:  BEGIN
         rgbtBlue   := Y;
         rgbtGreen := Y;
         rgbtRed    := Y
       END;
...

The conversion of YUV to RGB is in this case:

// YUV to RGB Conversion
// see www.webartz.com/fourcc/fccyvrgb.htm
// It's unclear whether U and V are switched, or whether
// the formula from this web site is correct with
// U = Cb and V = Cr
3:  BEGIN
       rgbtBlue    := FixRange( 1.164*(Y - 16) + 2.018*(U-128) );
       rgbtGreen   := FixRange( 1.164*(Y - 16) - 0.813*(U-128) - 0.391*(V-128) );
       rgbtRed     := FixRange( 1.164*(Y - 16) + 1.596*(V-128) );
     END;
...

The FixRange function enforces a "floor" of 0 and a "ceiling" of 255:

// R, G and B values must be in the range 0..255
FUNCTION FixRange(CONST x: Double): BYTE;
  VAR temp: INTEGER;
BEGIN
  temp := ROUND(x);
  IF       temp > 255
  THEN temp := 255
  ELSE
     IF       temp < 0
     THEN temp := 0;

  RESULT := temp
END;

The "Weather Lady" and "News Guy" RGB  images contain 62,193 and 45,090 unique colors, respectively.  (To count unique colors in an image, see the Show Image Lab Report).  Considerable extra coding is necessary to reduce the number of unique colors to only 236, which is what is needed for display in Windows 256 color display mode.  That is the main reason a pf24bit bitmap was used in this solution.  (For an example of how to use a pf8bit bitmap with palettes with a limited number of colors, see the Y8 example below).

By the way, the "Y" of YUV is the gamma corrected "brightness" component of the colour; the "Y" of the CIE XYZ model is linear (uncorrected) brightness. Both are related, but they are not the same.  

Acknowledgement
Thanks to Tarvo Hirs for providing me these images and asking how they could be processed.

His computer is a Celeron 300, 128MB RAM, 2x ATI ExpertXL PCI videocard, Windows98 with two displays.  His capture card is a ATI-TV add-on to the ATI videocard. It connects to the videocard via IDE format cable, so no bus bandwidth is used for the TV picture transport. The pictures are at maximum resolution for the card. 

The TV channel is Estonian TV3.  The picture quality is quite low because Tarvo didn't have a proper antenna.  He just dropped a cable out of the window (which explains the interference that can be see in the "V" images above -- perhaps  a good starting point to eliminate the interference with a FFT!). 

Conclusions
Humans can discriminate spatial monochrome information better than spatial color information.  Hence, more bits are used to store Y information since the human eye is more sensitive to changes in luminance than changes in hue or saturation, which is communicated in the U and V components.


Keywords
YUV, RGB, YVU-9, YV-12, Scanline, TRGBTripleArray, TFileStream, Seek, SaveToFile

Files
Delphi 3/4/5 Source Code and EXE with YUV9.Stream and YUV12.Stream files (319 KB):  YVUtoRGB.ZIP

Also, see Links below.


uyvy.gif (949 bytes) UYVY to RGB
ScreenUYVY.jpg (32480 bytes)

Purpose
The purpose of this program is to read a stream of UYVY bytes and convert the YUV values to RGB for display.

Materials and Equipment

Software Requirements
Windows 95/98
Delphi 3/4/5  (to recompile)
UYVY.EXE with input file
UYVY.Stream

Hardware Requirements
VGA (or higher) monitor in high color or true color display mode.

Procedure

  1. Double click on the UYVY.EXE icon to start the program.
  2. Click on the Read button to process the UYVY.Stream file and display it on the screen.
  3. If desired, view the UYVY.BMP file.

Discussion
One common YUV format group is called the packed formats group where Y, U and V samples are packed together into macropixels, which are stored in a single array.  (See the YVU-9 and YV-12 Lab Report above for a planar format example.)  One of these packed formats is UYVY, which  is probably the most popular of the various YUV 4:2:2 formats. 

Understanding the format of a digital video stream is essential in processing an image.   Determining the X and Y dimensions of an image in a file is the first step.  In this case, I was given the size of each image.

CONST
  YUVwidth   = 386;
  YUVheight  = 320;

(Acutally, the original image was 720-by-576.  This example is a subset of the original.)

Each row of data is an array of UYVY values, where each Y, U, V value is a single byte.  The BufferYUV array contains the array of RECORDs.

TYPE
  TYUVWord  =   // A "Word" is always 2-bytes in all versions of Delphi
  RECORD
    UV: BYTE;    // U if index even; V if index odd
    Y   : BYTE;   // Y for all array elements
  END;
...
VAR
  BitmapRGB : TBitmap;
  BufferYUV : ARRAY[0..YUVwidth-1] OF TYUVWord;

The rest of the program is relatively straigtforward:

TYPE
...
  TRGBTripleArray = ARRAY[0..YUVwidth-1] OF TRGBTriple;
  pRGBTripleArray = ^TRGBTripleArray;

VAR
  BitmapRGB : TBitmap;
...
  i : INTEGER;
  j : INTEGER;
  Row : pRGBTripleArray;
  StreamYUV : TFileStream;
  U : INTEGER;
  V : INTEGER;
  Y : INTEGER;

begin
  StreamYUV := TFileStream.Create('UYVY.Stream', fmOpenRead);

  TRY
    BitmapRGB := TBitmap.Create;
    TRY
      BitmapRGB.PixelFormat := pf24Bit;
      BitmapRGB.Width := YUVwidth;
      BitmapRGB.Height := YUVheight;

      FOR j := 0 TO YUVheight-1 DO
      BEGIN
        StreamYUV.Read(BufferYUV, SizeOf(BufferYUV));
        Row := BitmapRGB.Scanline[j];

        FOR i := 0 TO YUVwidth-1 DO
        BEGIN
          IF        i MOD 2 = 0 // Even
          THEN BEGIN
            Y := BufferYUV[i].Y;
            U := BufferYUV[i].UV;
            V := BufferYUV[i+1].UV // get V from next word
          END
          ELSE BEGIN // Odd
            Y := BufferYUV[i].Y;
            U := BufferYUV[i-1].UV; // get U from last word
            V := BufferYUV[i].UV
          END;

          WITH Row[i] DO
          BEGIN
            // See "HELP! YUV-RGB conversion", 12/12/97 by
            // lweng@analogic.com for this formula:
            rgbtRed := FixValue(1.164*(Y-16) + 1.596*(V-128));
            rgbtGreen := FixValue(1.164*(Y-16) - 0.392*(U-128) - 0.813*(V-128));
            rgbtBlue := FixValue(1.164*(Y-16) + 2.017*(U-128))
          END
        END

      END;

      BitmapRGB.SaveToFile('UYVY.BMP');
      ImageYUV.Picture.Graphic := BitmapRGB
    FINALLY
      BitmapRGB.Free
    END

  FINALLY
    StreamYUV.Free
  END
end;

The FixValue function forces a floor of 0 and a ceiling of 255 on the R, G, B values:

FUNCTION FixValue(CONST x: DOUBLE): BYTE;
  VAR
    i: INTEGER;
BEGIN
  i := ROUND(x);
  IF       i > 255
  THEN RESULT := 255
  ELSE
    IF       i < 0
    THEN RESULT := 0
    ELSE RESULT := BYTE(i)
END {FixValue};

Acknowledgement
Thanks to Daniel Halan from Sweden for providing me this image and asking how it could be processed.

Conclusions
A byte stream with UYVY values can be easily converted to RGB pixels for display.


Keywords
YUV, UYVY, RGB, TYUVWord, TFileStream, Scanline, TRGBTripleArray

Files
Delphi 3/4/5 Source and EXE with UYVY.Stream file (259 KB):   UYVY.ZIP

Also, see Links below.


grays.gif (1078 bytes) Y8 Gray Scale
ScreenDeutschGray.jpg (34529 bytes)

Purpose
The purpose of this program is to read a stream of bytes and display the information as a gray scale image.  Both palette and non-palette solutions are presented.

Materials and Equipment

Software Requirements
Windows 95/98
Delphi 3/4/5  (to recompile)
DeutschGray.EXE with input file
Gray.Stream

Hardware Requirements
VGA (or higher) monitor in high color or true color display mode.

Procedure

  1. Double click on the DeutschGray.EXE icon to start the program.
  2. Click on the left "Read Image" button to display a solution that produces a pf24bit BMP file.
  3. Click on the right "Read Image" button to display a solution that uses palettes and produces a pf8bit BMP file.

Discussion
Understanding the format of a digital video stream is essential in processing an image.   Determining the X and Y dimensions of an image in a file is the first step.  In this case, I determined the the size of the image from information that was given me, and by visually inspecting a hex dump of the image bytes.

CONST
  BildWidth = 392;
  BildHeight = 250;

(Actually, this image is a small portion of the original image.  This is to reduce the size of the download file.)

Creating a 256 shades-of-gray image using a pf24bit TBitmap (for display in high color or true color mode) is fairly straightforward.  Working with a pf24bit bitmap requires the use of a pRGBTripleArray to access each pixel in a scanline Row:

// Read file and display as pf24bit bitmap -- avoid use of palettes
procedure TForm1.ButtonGray1Click(Sender: TObject);
  TYPE
    TRGBTripleArray = ARRAY[0..BildWidth-1] OF TRGBTriple;
    pRGBTripleArray = ^TRGBTripleArray;
  VAR
    Bitmap : TBitmap;
    GrayBuffer: pByteArray; // ARRAY[0..BildWidth-1] OF BYTE;
    GrayStream: TFileStream;
    i : INTEGER;
    j : INTEGER;
    Row : pRGBTripleArray;
begin
  // Allocate buffer to read picture file
  GetMem(GrayBuffer, BildWidth);

  TRY
    Bitmap := TBitmap.Create;
    TRY
      Bitmap.PixelFormat := pf24bit; // avoid palettes
      Bitmap.Width := BildWidth;
      Bitmap.Height := BildHeight;

      GrayStream := TFileStream.Create('Gray.Stream', fmOpenRead);

      TRY
        FOR j := 0 TO BildHeight-1 DO
        BEGIN
          GrayStream.Read(GrayBuffer^, BildWidth);

          Row := Bitmap.Scanline[j];
          FOR i := 0 TO BildWidth-1 DO
          BEGIN
            WITH Row[i] DO
            BEGIN
              rgbtBlue     := GrayBuffer[i];
              rgbtGreen := GrayBuffer[i];
              rgbtRed     := GrayBuffer[i]
            END
          END

        END;
        // Display image (or save to file here)
        Image.Picture.Graphic := Bitmap;

        // Save to file
        Bitmap.SaveToFile('Graypf24bit.BMP')

      FINALLY
        GrayStream.Free;
      END

    FINALLY
      Bitmap.Free
    END

  FINALLY
    FreeMem(GrayBuffer)
  END
end;

The key to displaying a shades-of-gray pixel is to assign the R, G and B components of the pixel the same value.  When the input range of values for the byte GrayBuffer[i] is from 0 to 255, assigning the same value to R, G and B results in 256 shades of gray.  This pf24bit bitmap solution ignores any gamma correction.

This pf24bit solution will work fine when using high color or true color display modes, but in 256-color display mode, 20 of the 256 colors are reserved by Windows for display of buttons, icons, scroll bars, etc..  This leaves only 236 colors to work with.  The input range of 0 to 255 must be mapped to one of the 236 pallete values and these 236 palette entries must span the full 256 possible gray scale values. (Sounds almost like double talk?)

Normally Window reserves the first 10 and last 20 entries in the 256 color palette table.  That convention will be followed here by defining the palette entries from 10 to 245.

First, let's define a LookUp table.  Input byte values 0 to 255 will be mapped to 10 to 245 in this Lookup table:

VAR
  k : INTEGER;
  LookupTable: ARRAY[0..255] OF BYTE;
...
Windows palette limitation for 256 color display. The full range
// 0..255 can be used if only high color or true color display are used.
// Lookup table to map gray scale values from 0 to 255 to
// 10 to 245. This skips the first 10 and last 10 palette slots,
// which contain the fixed windows colors.
FOR k := 0 TO 255 DO
BEGIN
  LookupTable[k] := 10 + MulDiv(k, 235, 255)
END;

Now let's define the 236 palette entries from 10 to 245 to span the range RGB(0,0,0) to RGB(255,255,255).  [Note:  One could change this table to reflect a gamma correction, if desired.]

// Assume Windows "fixed" colors are reserved for 256 color display mode.
// That is, reserve first and last 10 palette entries for system palette.
FUNCTION DefineShadesOfGrayPalette: hPalette;
  CONST
    PaletteVersion = $0300; // "Magic Number" for Window's LOGPALETTE
  VAR
    i : INTEGER;
    LogicalPalette : TMaxLogPalette;
    ScreenDeviceContext: hDC;
BEGIN
  LogicalPalette.palVersion := PaletteVersion;
  LogicalPalette.palNumEntries := 256;

  ScreenDeviceContext := GetDC(hWnd_DeskTop);
  TRY
    // Get first and last 10 entries from SystemPalette
    GetSystemPaletteEntries(ScreenDeviceContext, 0, 10,
    LogicalPalette.palPalEntry[0]);
    GetSystemPaletteEntries(ScreenDeviceContext, 246, 10,
    LogicalPalette.palPalEntry[246])
  FINALLY
    ReleaseDC(0, ScreenDeviceContext)
  END;

  // Skip over first 10 and last 10 "fixed" SystemPalette entries
  FOR i := 0 TO 255-20 DO
  BEGIN
    // Stetch palette entries 0..235 to span gray scale range 0..255
    LogicalPalette.palPalEntry[10+i].peRed    := MulDiv(i, 255, 235);
    LogicalPalette.palPalEntry[10+i].peGreen := MulDiv(i, 255, 235);
    LogicalPalette.palPalEntry[10+i].peBlue    := MulDiv(i, 255, 235);
    LogicalPalette.palPalEntry[10+i].peFlags := PC_NOCOLLAPSE;
  END;
  RESULT := CreatePalette(pLogPalette(@LogicalPalette)^);

END {DefineShadesOfGrayPalette};

Working with a pf8bit bitmap requires the use of a pByteArray to access each pixel in a scanline Row.  With this palette definition in place, the rest of the palette solution is much like the pf24bit solution.

VAR
  Bitmap : TBitmap;
  GrayBuffer : pByteArray; // ARRAY[0..BildWidth-1] OF BYTE;
  GrayStream : TFileStream;
  i : INTEGER;
  j : INTEGER;
  k : INTEGER;
  LookupTable: ARRAY[0..255] OF BYTE;
  Row : pByteArray;
...
// Allocate buffer to read picture file
GetMem(GrayBuffer, BildWidth);

TRY
  Bitmap := TBitmap.Create;
  TRY
    Bitmap.PixelFormat := pf8bit;
    Bitmap.Width := BildWidth;
    Bitmap.Height := BildHeight;

    Bitmap.Palette := DefineShadesOfGrayPalette;

    GrayStream := TFileStream.Create('Gray.Stream', fmOpenRead);
    TRY
      FOR j := 0 TO BildHeight-1 DO
      BEGIN
        GrayStream.Read(GrayBuffer^, BildWidth);
        Row := Bitmap.Scanline[j];
        FOR i := 0 TO BildWidth-1 DO
        BEGIN
          Row[i] := LookupTable[ GrayBuffer[i] ]; // one byte per pixel
        END

      END;

      // Display image
      Image.Picture.Graphic := Bitmap;

      // Save to file
      Bitmap.SaveToFile('Graypf8bit.BMP')

    FINALLY
      GrayStream.Free
    END

  FINALLY
    Bitmap.Free
END

FINALLY
FreeMem(GrayBuffer)
...

This example only involves 182 shades of gray, so the limitation of 236 unique colors in a Windows' palette doesn't affect the image much.

To use palettes correctly with multiple Windows present, extra code is needed to handle WM_PALETTECHANGED and WM_QUERYNEWPALETTE messages. See the Palette Lab for additional information.  These messages are not handled in this program.

If necessary, histogram stretching could be used to improve contrast -- see efg's HistoStretch Grays Lab Report.

The image is from a German (Bertelsmann) Channel 7 called "Pro 7."

Acknowledgement
Thanks to Sebastian Grabert from Germany for providing me this image and asking how it could be processed.

Conclusions
Given an image byte stream representing shades of gray from 0 to 255, display is easy with a pf24bit bitmap in high color or true color display mode, but requires considerable additional coding for display in 256-color mode with a pf8bit bitmap.


Keywords
Gray Scale, Y8, pRGBTripleArray, pByteArray, TFileStream, Scanline, TMaxLogPalette, GetSystemPaletteEntries, Lookup Table

Files
Delphi 3/4/5 Source Code and EXE with Gray.Stream data file (187 KB):   DeutschGray.ZIP


YYYYCrCb

411 to BMP

Purpose
The purpose of this program is demonstrate how to display the 411 file used with a Sony Mavica camera.

Materials and Equipment

Software Requirements
Windows 95/98
Delphi 3/4/5  (to compile)
C411toBMP source code
Sample 411 file

Hardware Requirements
VGA monitor in high color or true color display mode.

Procedure

  1. Compile the C411toBMP project in D3 - D5.  Select Run.
  2. Press the Read 411 button and select the sample 411 file.
  3. The thumbnail image is displayed..

Discussion

The byte stream for the 411 image consists of groups of six bytes:  Y0 Y1 Y2 Y3 Cb Cr.  Each group of six bytes is converted into four TRGBTriples in a pf24bit Scanline as follows:

USES
  FileCtrl;  // FileExists
// Delphi version of C program originally
// found at www.hamrick.com/mavica/4112bmp.txt 
FUNCTION Convert411ToBMP(CONST filename:  STRING):  TBitmap;
  TYPE
    TYCbCr =
    RECORD
      Y:   ARRAY[0..3] OF BYTE;
      Cb:  BYTE;
      Cr:  BYTE
    END;
    TRGBTripleArray =  ARRAY[WORD] OF TRGBTriple;
    pRGBTripleArray = ^TRGBTripleArray;
  CONST
     Width  = 64;   // hardwired for 411 files
     Height = 48;
  VAR
    Cb,Cr        :  INTEGER;
    FilePointer :  INTEGER;
    FileStream :  TFileStream;
    i,j,k            :  INTEGER;
    R,G,B        :  INTEGER;
    Row          :  pRGBTripleArray;
    YCbCr       :  TYCbCr;
BEGIN
  RESULT := TBitmap.Create;
  RESULT.PixelFormat := pf24bit;
  RESULT.Width  := Width;
  RESULT.Height := Height;
  RESULT.Canvas.FillRect(RESULT.Canvas.ClipRect);
  // If file does not exist, a white bitmap will be returned
  IF   FileExists(filename)
  THEN BEGIN
    FileStream := TFileStream.Create(filename, fmOpenRead + fmShareDenyNone);
    TRY
      FilePointer := 0;
      FileStream.Seek(FilePointer, soFromBeginning);
      // 6 bytes in 411 file for each 4 pixels:  Y0 Y1 Y2 Y3 Cb Cr
      FOR j := 0 TO RESULT.Height-1 DO
      BEGIN
        Row  := RESULT.Scanline[j];
        FOR i := 0 TO (RESULT.WIDTH DIV 4)-1 DO
        BEGIN
          FileStream.Read(YCbCr, SizeOf(TYCbCr));
          Cb := YCbCr.Cb - 128;
          Cr := YCbCr.Cr - 128;
          FOR k := 0 TO 3 DO
          BEGIN
            R := YCbCr.Y[k] + Round(1.40200*Cr);
            G := YCbCr.Y[k] - ROUND(0.34414*Cb + 0.71414*Cr);
            B := YCbCr.Y[k] + ROUND(1.77200*Cb);
            IF   R > 255
            THEN R := 255
            ELSE
              IF   R < 0
              THEN R := 0;
            IF   G > 255
            THEN G := 255
            ELSE
              IF   G < 0
              THEN G := 0;
            IF   B > 255
            THEN B := 255
            ELSE
              IF   B < 0
              THEN B := 0;
            row[4*i+k].rgbtRed   := R;
            row[4*i+k].rgbtGreen := G;
            row[4*i+k].rgbtBlue  := B
          END
        END
      END
    FINALLY
      FileStream.Free
    END
  END
END {Convert411toBMP};

Acknowledgement
Thanks to Peter Mora for the sample image and asking about the conversion.

Conclusions
Now I just need a Sony Mavica camera!


Keywords
411, TBitmap, YCrCb, TRGBTripleArray, TFileStream

Files
Delphi 3/4/5 Source Code and sample 411 file:   411.ZIP


Links

YCC colour space and image compression
http://astronomy.swin.edu.au/~pbourke/colour/ycc 

Greg Blair's UseNet Post with C routines RGBto422 and RGB420

efg's Color Reference Library


Updated 30 May 2007


since 11 Sep 1999