Row-Stride. What is it good for?
In version 3.0, ImgSource embraced the "rowstride" concept. Nearly all of the
image processing functions now take one or more uRowStride parameters. Why ?
What is row stride?
"Row stride" is simply the number of bytes in a pixel row. By adding this number to the current pixel position,
ImgSource can move down one row in the image. For an RGB image like those ImgSource typically creates, the row
stride is width * 3 bytes. That is, each pixel row takes up width * 3 bytes. Likewise, the 8-bit images
that ImgSource favors have width bytes in each pixel row. And finally one-bit images require
(width + 7) / 8 bytes per pixel row (ie. round up to the next highest multiple of 8 bits, to assure that
each row starts on a new byte).
What's the benefit?
In version 3.0, ImgSource allows (and requires) you to explicitly provide the row stride for most images.
There are two main reasons:
- It allows you to operate on partial images. In most image processing programs (Photoshop, Paint Shop Pro, etc.) you can select a rectangular area
of the image and perform an operation on that section only. In previous versions of ImgSource, you would have to copy that sub-section out of the image,
process the cropped portion, then overlay it back into the source image. That's too much work. What you really need is to
be able to apply effects to sub-sections of the image, in-place. ImgSource 3.0 can do this.
- It allows functions to operate on DIBs. A DIB is Windows' native image format. There is a great benefit in being able to
operate on these directly, rather than having to convert to an intermediate form and back.
Partial image operations
Here's a 10 x 10 grayscale image:
Each row is 10 pixels wide, one byte per pixel. So, the row stride is 10. As an example, let's
change the brightness of the entire image, using IS3ApplyBrightnessContrastToImage:
// rowstride = width * 1 . 1 = bytes per pixel. for RGB this would be 3, for RGBA 4, etc..
UINT32 uRowStride = width * 1;
IS3ApplyBrightnessContrastToImage(pImage,
width, height,
1, // one byte per pixel
uRowStride,
0.5, // increase brightness by +0.5
0, // don't change contrast
0);
|
And the result, as expected:
That's how you process the whole image. But we want to process a subsection. The simplest way of changing
a part of the image is to tell ImgSource that the image actually starts in a different place. You can do this
in all versions of ImgSource. It works like this:
Processing the middle rows of an image
// rowstride = width * 1
UINT32 uRowStride = width * 1;
IS3ApplyBrightnessContrastToImage(pImage + uRowStride * 3, // add 3 rows to the start address
width, 4, // tell ImgSource that the image is only 4 rows high
1, // one byte per pixel
uRowStride,
0.5, // increase brightness by +0.5
0, // don't change contrast
0);
|
By offsetting the image start address we can skip entire image rows from the top. And by adjusting the height
that we give ImgSource, we can cause it to process fewer rows. In this case, ImgSource will operate on the
section shaded in blue:
The result is, as expected:
That's interesting, though it's not likely that you'll need to process the middle rows of an image very often.
More likely, you would want to process an arbitrary rectangle anywhere in the image, not just a full-width strip out
of the middle. You can do this with ImgSource v3.0, but you need to be clever about it.
Processing an arbitrary rectangle from an image
// rowstride = width * 1
UINT32 uRowStride = width * 1;
// add 4 rows and 4 columns to the start address
BYTE *pImageStartAddress = pImage + uRowStride * 4 + (4 * 1);
IS3ApplyBrightnessContrastToImage(pImageStareAddress,
3, // tell ImgSource that the image is only 3 pixels wide
3, // tell ImgSource that the image is only 3 rows high
1, // one byte per pixel
uRowStride,
0.5, // increase brightness by +0.5
0, // don't change contrast
0);
|
Notice what we've done here. First, we've added an offset of 4 to the start address. This is the number of bytes required
for four pixels (in our 1-byte per pixel image). Next, we've told ImgSource that the image is only three pixels wide, and three
pixels high. This is cleary not true, but it will give us the results we want. To understand how this works, and how you can
use this technique in your own applications, you first need to understand how ImgSource uses the row stride.
How ImgSource uses row stride:
1. start at the first pixel in the first row
2. processes "width" pixels in that row
3. find the next row
4. goto 2
Or, in pseudo-code:
// loop over rows. our first row starts at the start of the image
pCurrentRowAddress = pImageStartAddress
for (row = 0; row < height; row = row + 1)
// loop over columns. start each pixel column at the start of the row
pCurrentPixelAddress = pCurrentRowAddress
for (col = 0; col < width; col = col + 1)
// process pCurrentPixel
...
// find the next pixel by adding the number of bytes in a pixel to the current pixel address
pCurrentPixelAddress = pCurrentPixelAddress + uBytesPerPixel
next col
// when we get here, we've processed "width" pixels on the current row.
// find the next row by adding the number of bytes in a row to the current row address.
// this is the key.
pCurRowAddress = pCurRowAddress + uRowStride
next row
// when we get here, we've processed "width" columns on "height" rows.
|
Here it is, step by step:
- By offsetting the start address, we've told ImgSource that our image starts four rows and four columns from where
it actually starts. So,
pImageStartAddress points to pixel (4, 4).
- We've set the width to 4. This means ImgSource will only process 4 pixels in each row before moving to the next row. Note
that this is a slightly different interpretation of "width" than previous versions of ImgSource used. In previous version, when
you specified width, you have to provide the entire image width; internally, ImgSource would calculate the row stride on its own,
using the width and the number of bytes per pixel, and it always operated on the entire row.
- We've set the height to 4. This is similar to the second example where we processed the middle of the image. It tells ImgSource
how many rows to process (which may be less than the number of rows in the actual image).
- We've given the row stride, so that ImgSource knows how to get from one pixel row to the next.
So, all of these together mean that ImgSource will proces the area shaded in blue:
Other uses
That's the basic technique. It can be used in nearly any ImgSource function that accepts a rowstride parameter and operates
in-place. If the function generates an output image different from the input image (ie. to a different buffer), you can do even more
interesting things. First, you can, as above, adjust the input image start and width/height to process a sub-section of the source image.
But, in many cases, you can adjust the output start address and dimensions to cause the output to go to a sub-section of the overall
output image:
Or, as long as the sections don't overlap, you can even process from one part of the image to another:
Crop, process overlay
The technique above, in effect, implements cropping as part of the overall operation. So, you can crop, process and overlay an image
in one operation. This can greatly improve the performance of your application.
Crop + resize = zoom
For another interesting use, consider the resizing functions. In ImgSource v3.0, all resizing functions take a row stride parameter
for both input and output images. So, as above, you can resize from the source image into a subsection of the destination image. The
output is a zoomed-in section of the input. This is also much faster than a crop, resize and overlay. Here's how to do a crop, resize and
overlay using the row stride techniques:

// assume the input image has been loaded from a file and is inWidth x inHeight pixels, RGB
UINT32 outWidth = 100, outHeight = 100;
// allocate a 100x100 output image and fill it with dark red
UINT32 uOutRowStride = outWidth * 3;
BYTE *pOut = new BYTE[uOutRowStride * outHeight];
RECT fillRect = {0,0,outWidth - 1, outHeight - 1};
IS3FillSolidRect(pOut, outWidth, outHeight, 3, uOutRowStride, fillRect, RGB(128,0,0), 0);
// now, offset the start of the source image to (50,50)
BYTE *pInputSectionStart = pInput + // input image start
uInRowStride * 50 + // offset 50 rows
(50 * 3); // offset 50 RGB pixels
// offset the start of the output image to (10,10)
BYTE *pOutputSectionStart = pOut + // output image start
uOutRowStride * 10 + // offset 10 rows
(10 * 3); // offset 10 RGB pixels
IS3ResizeImage(pInputSectionStart,
150, 100, // take a 150 x 100 section of the input
uInRowStride,
pOutputSectionStart,
80, 80, // output into an 80x80 section of the output.
// this gives a 10 pixel border (100 pixels total,
// start offset of 10 + 80 leaves 10 on the other side)
uOutRowStride,
3);
delete [] pOut;
|
Note that this example uses RGB images, so the row strides are the widths times three.
DIBs
A DIB is Windows' native image format. ImgSource previously provided functions to convert DIBs to and from RGB and colormapped
images. The conversions are sometimes complex, however, and can add a lot of processing time. In ImgSource 3, most of the image
processing functions can work directly on many kinds of DIBs, as long as you pay close attention to a few rules.
- All DIBs start with a header. The typical DIB starts with a BITMAPINFOHEADER struct, though Microsoft has introduced other flavors that aren't very widespread, yet.
You need to account for the size of this header, in order to find the start of the pixels. You can use IS3DIBPixelStart to find the start of the pixels.
- DIB row stride is always a multiple of four bytes. You can get the exact row stride by using IS3GetDIBRowStride.
- DIBs are vertically flipped. The bottom row in the image comes first in memory. Unless the DIB has a negative height, in
which case the first row comes first in memory. Note that this is only a vertical flip, not a horizontal flip.
- For 24 and 32 bits DIBs, the pixels are arranged in Blue, Green, Red (BGR) order - reversed from the RGB order that ImgSource uses.
- You can access the DIB's palette (if there is one) with IS3GetDIBPalette.
- ImgSource cannot operate directly on the pixels in compressed DIBs, 4-bit, 16-bit or either of the JPG-in-DIB or PNG-DIB flavors. For these DIBs, you should convert to a format that ImgSource does understand, like 8-bit colormapped or RGB.
As noted above, DIBs are arranged so that each pixel row is a multiple of four bytes wide. This means there can be up to 3 full bytes of padding at the end of each row.
As of version 3, most ImgSource image processing functions can accommodate these images because of the row stride parameter. The RGB/BGR reversal can be handled by most
functions where color order matters.
Reducing the color depth of a DIB
This is a sample of how to make a copy of a DIB with reduced color depth, using IS3QuantizeRGBTo8Bit. The bulk of the work here is in allocating and identifying the
parts of the input and output DIBs. The actual color depth reduction is a single function call.
UINT32 w, bc;
__int32 h;
// load a DIB
HISSRC hSrc = IS3OpenFileSource(pFileName);
HGLOBAL hImg = IS3ReadImageToDIB(hSrc);
IS3CloseSource(hSrc);
// get a BITMAPINFOHEADER pointer
BITMAPINFOHEADER *pInDIB = (BITMAPINFOHEADER *)hImg;
// get the dimensions of the input DIB
IS3DIBWidth(pInDIB, &w);
IS3DIBHeight(pInDIB, &h);
// this sample only works with 24-bit input
IS3DIBBitCount(pInDIB, &bc);
if (bc !=24 )
{
return;
}
// allocate room for the output
UINT32 uColors = 256;
UINT32 uOutDIBSize = ISGetISDIBSize(w, h, 8, uColors);
// number of bytes in an output row
UINT32 uOutRowStride = IS3GetDIBRowStride(w, 8);
BYTE *pDIBBuf = new BYTE[uOutDIBSize];
BITMAPINFOHEADER *pOutDIB = (BITMAPINFOHEADER *)pDIBBuf;
// fill out the DIB header
pOutDIB->biSize = sizeof(BITMAPINFOHEADER);
pOutDIB->biWidth = w;
pOutDIB->biHeight = h;
pOutDIB->biPlanes = 1;
pOutDIB->biBitCount = 8;
pOutDIB->biCompression=BI_RGB;
pOutDIB->biSizeImage= uOutRowStride * h;
pOutDIB->biXPelsPerMeter=0;
pOutDIB->biYPelsPerMeter=0;
pOutDIB->biClrUsed = uColors;
pOutDIB->biClrImportant = 0;
// number of bytes in an input row
UINT32 uInRowStride = IS3GetDIBRowStride(w, 24);
// where do the pixels start?
BYTE *pInPixels = IS3DIBPixelStart(pInDIB);
BYTE *pOutPixels = IS3DIBPixelStart(pOutDIB);
// where is the output palette?
RGBQUAD *pOutPal = IS3GetDIBPalette(pOutDIB, &uColors);
// reduce the color depth
IS3QuantizeRGBTo8Bit(pInPixels, w, h, uInRowStride,
pOutPixels, uOutRowStride,
uColors, pOutPal,
1,
1); // this flag specifies BGR output
// clean up
delete [] pDIBBuf;
GlobalFree(hImg);
|
So, the important points here are:
- Identify the start of the pixels for each DIB
- Determine the row stride for each DIB
- Fill out the output DIB header (BITMAPINFOHEADER)
- Call the image processing function (IS3QuantizeRGBTo8Bit in this case) with the appropriate pointers.
Note that we could also, if we wanted to make this example a bit more complex, do the same kind of sub-region processing that
was shown in the sections above. It's just a matter of setting the appropriate pointers and sizes.
Copyright © 2008, Smaller Animals Software, Inc.
|
Spam emails
A spammer is using our email domain for the return address on his spams. There is nothing we can do to stop this, unfortunately.
But, we want to assure you that Smaller Animals Software would never send emails with links to pornography, with virus attachments, or in any language but English. We do not send unsolicited mass email.
The spam is not from us.
|