DirectShow and Windows do not support 12- and 16-bit grayscale bitmaps. LEAD has defined the LGRY video format to enable the use of such bitmaps using the DirectShow architecture. This is used in the DICOM filters to generate grayscale video with more than 8 bits per pixel.
When describing compressed 12/16-bit data, indicate the media type format is using the LGRYVideoInfo structure by setting the media subtype to FORMAT_LGRYVideoInfo. When describing uncompressed 12/16-bit data, indicate the media type format is using the LGRYVideoInfo structure by setting the biCompression to LGRY. But even in this case, it is better to also set the media subtype to FORMAT_LGRYVideoInfo.
The following define statements and structures can be found in <INSTDIR>\Include\LGRYType.H:
static GUID FORMAT_LGRYVideoInfo = { 0x48799704, 0x2735, 0x4be6, { 0xa0, 0xce, 0xdd, 0xc6, 0x2c, 0x25, 0xa4, 0xf2 } };
The formattype member of the media type must be set to FORMAT_LGRYVideoInfo (or ltmmFORMAT_LGRYVideoInfo) to indicate the use of the LGRYVIDEOINFO structure to describe 12- or 16-bit compressed data. It is not necessary to set the formattype to indicate uncompressed 12/16-bit data, but it is recommended to do so even for uncompressed data.
Note: FORMAT_LGRYVideoInfo is defined in LGRYType.h, while ltmmFORMAT_LGRYVideoInfo is defined in ltmm.h.
typedef struct
{
RECT rcSource;
RECT rcTarget;
DWORD dwBitRate;
DWORD dwBitErrorRate;
REFERENCE_TIME AvgTimePerFrame;
LGRYINFOHEADER bmiHeader;
RGBQUAD bmiColors[65536];
} LGRYVIDEOINFO, FAR*pLGRYVIDEOINFO;
Describes the format of a grayscale video format:
Member |
Description |
rcSource |
The image portion to use |
rcTarget |
Target location for the video |
dwBitRate |
Approximate bit data rate |
dwBitErrorRate |
Bit error rate for this stream |
AvgTimePerFrame |
Average time per frame (100ns units) |
bmiHeader |
Structure containing grayscale image information. |
bmiColors |
Remapping lookup table (LUT). This is optional. This field is ignored unless bmiHeader. biClrUsed is non-zero. |
typedef struct tagLGRYINFOHEADER
{
/* BITMAPINFOHEADER fields */
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
/* Fields specific only to the LGRY format */
LONG biLowBit;
LONG biHighBit;
LONG biMinVal;
LONG biMaxVal;
LONG biWindowWidth;
LONG biWindowCenter;
double biRescaleIntercept;
double biRescaleSlope;
DWORD biFlags;
LONG nReserved1;
LONG nReserved2;
LONG nReserved3;
} LGRYINFOHEADER, FAR*pLGRYINFOHEADER;
Describes the grayscale image.
Member |
Description |
biSize |
Size of this structure. Should be sizeof(LGRYINFOHEADER). |
biWidth |
Video width. |
biHeight |
Use positive value to specify a top-down image. This is the opposite of the Windows convention for uncompressed RGB images. |
biPlanes |
Should be 1 |
biBitCount |
Should be 12 or 16: |
biCompression |
Should be mmioFOURCC('L','G','R','Y') to indicate uncompressed data. Should be mmioFOURCC('L','j','p','g') to indicate lossless JPEG data. Should be mmioFOURCC('J','P','E','G') or mmioFOURCC('M','J','P','G') to indicate lossy JPEG data. When using JPEG data, set the formattype to FORMAT_LGRYVideoInfo or ltmmFORMAT_LGRYVideoInfo. |
biSizeImage |
Size of the image in bytes. The size of each line needs to be a multiple of 4 bytes, so padding might be required. The formula for calculating this field properly is: biSizeImage = ((((biWidth * biBitCount) + 31) & ~31) / 8) * abs(biHeight) |
biXPelsPerMeter |
Specifies the horizontal resolution, in pixels per meter. |
biYPelsPerMeter |
Specifies the vertical resolution, in pixels per meter. |
biClrUsed |
0 if the LUT is not present. In this case, the bmiColors field is not used/needed. != 0 if there is a LUT. In this case, bmiColors contains the LUT. biClrUsed should be equal to 1<<(biHighBit - biLowBit + 1) |
biClrImportant |
Not currently used. Set this value to 0 |
biLowBit |
Low bit for the pixel data (0 <= biLowBit <= biHighBit <= biBitCount-1). |
biHighBit |
High bit for pixel data. |
biMinVal |
Minimum value of the pixel data only (ignores the non-pixel bits). |
biMaxVal |
Maximum value of the pixel data only (ignores the non-pixel bits). |
biWindowWidth |
DICOM window width. This can be used to regenerate the LUT. |
biWindowCenter |
DICOM window center. This can be used to regenerate the LUT. |
biRescaleIntercept |
DICOM rescale intercept. This can be used to regenerate the LUT. |
biRescaleSlope |
DICOM rescale slope. This can be used to regenerate the LUT. |
biFlags |
Indicates which fields are valid. Possible values are: LGRY_RESCALE_VALID [0x0001] if set, biRescaleIntercept and biRescaleSlope are valid LGRY_WINDOW_VALID [0x0002] if set, biWindowSize and biWindowCenter are valid LGRY_BITRANGE_VALID [0x0004] if set, biLowBit and biHighBit are valid LGRY_MINMAX_VALID [0x0010] if set, biMinVal and biMaxVal are valid LGRY_LUT_ISGENERATED [0x0020] If there is a LUT: If set, the LUT is generated from the valid LGRY-specific fields. If not set, the LUT cannot be regenerated from the other LGRY fields. In this case, the LUT needs to be saved in the file. LGRY_LUT_ISGRAYSCALE [0x0040] If there is a LUT and this flag is set, then the LUT contains only grayscale values. This flag is set to avoid going through the LUT every time to detect whether the LUT contains only grayscale colors. LGRY_LUT_ISCOLOR [0x0080] If there is a LUT and this flag is set, then the LUT contains non-grayscale values. This flag is set to avoid going through the LUT every time to detect whether the LUT contains only grayscale colors. LGRY_LUT_COLORMASK [0x00C0] This is NOT a flag. It is a MASK which can be used to detect whether a LUT is color, grayscale or unknown. |
nReserved1 |
Reserved for future use. |
nReserved2 |
Reserved for future use. |
nReserved3 |
Reserved for future use. |
The 12-bit data is packed: every 2 pixels occupy 3 bytes.
For example, if you have (2) 12-bit values and their hex values are 0xABC and 0xDEF, they will be stored in memory like this: 0xBC 0xFA 0xDE
This packing has the advantage that you can get each value on Intel processors with one read and one logical operation ("and" with 0xFFF for first value, shift right 4 bits for the second value).
The following macros can be used to deal with 12-bit packed data (keep in mind that these are C multi-line macros and it is important that they have "\" at the end of the line):
// helper macro for incrementing a pointer
#define PTR_INC(p) (((BYTE*)p)+1)
// read first 12-bit value
#define GET12_0(p) ((*(WORD*)p) & 0xFFF)
// read second 12-bit value
#define GET12_1(p) ((*(WORD*)PTR_INC(p)) >> 4)
// write two 12-bit values
#define PUT12(p, val0, val1) *(WORD*)p = (((val0) & 0xFFF) |
((WORD)(val1) << 12)); \
((BYTE*)p)[2] = ((DWORD)(val1) >> 4)
// write the first 12-bit value
#define PUT12_0(p, val0) *(WORD*)p = ( (*(WORD*)(p) & 0xF000) | \
((val0)& 0xFFF))
// write the second 12-bit value
#define PUT12_1(p, val1) *(WORD*)PTR_INC(p) = \
((*(WORD*)PTR_INC(p) & 0xF) | \
((WORD)(val1) << 4))
The following example describes how to set the various fields to describe grayscale 16-bit data in which only the lower 10 bits are to be used:
LGRYVIDEOINFO info; /* warning: do not put this on stack, as it is very large */
info.bmiHeader.biSize = sizeof(LGRYINFOHEADER);
info.bmiHeader.biWidth = VideoWidth;
info.bmiHeader.biHeight = VideoHeight; /* should be positive for normal (unflipped video) */
info.bmiHeader.biPlanes = 1;
info.bmiHeader.biBitCount = 16; /* I have 16 bits per pixel, although only the lower 10 bits contain useful data */
info.bmiHeader.biCompression = mmioFOURCC('L', 'G', 'R', 'Y');
info.bmiHeader.biSizeImage = (((VideoWidth * 2) + 3) & ~3) * VideoHeight;
info.bmiHeader.biClrUsed = 0; /* there is no LUT */
info.bmiHeader.biClrImportant = 0; /* not used right now. Set this value to 0 */
/* Fields specific only to the LGRY format */
info.bmiHeader.biLowBit = 0; /* low bit for the pixel data (0 <= biLowBit <= biHighBit <= biBitCount-1) */
info.bmiHeader.biHighBit = 9; /* 10-1 = 9. high bit for pixel data. */
info.bmiHeader.biFlags = LGRY_BITRANGE_VALID; /* only biLowBit and biHighBit are valid */
/* Since this case does not use the LUT, it is enough to set only the fields of LGRYVIDEOINFO up to and including bmiHeader. The useful size of the
LGRYVIDEOINFO structure is offsetof(LGRYVIDEOINFO, bmiColors). */