2

I have an RGB image such that:

Img[3*(row*imgWidth+column)+0], //R
Img[3*(row*imgWidth+column)+1], //G
Img[3*(row*imgWidth+column)+2]  //B

represent the intensity value for each pixel for each RGB. What is a clean method to pad borders of 0's(0-255 scale) around the image? The border can be adjusted to any width.

The only thing I can come up with is pretty much manual insertion of rows and columns, which becomes a very tediously long piece of code.

sorry, but libraries are not what I'm looking for here

mugetsu
  • 4,228
  • 9
  • 50
  • 79
  • I would probably create a new image sized to include the borders, fill it with black, then copy the old image on top of it in the right position. – Retired Ninja Sep 05 '12 at 00:32
  • Assuming you bit-blit this with whatever graphics/display library you're using, you could also 1) Display the image, then 2) do a "DrawRect()" around the image. ALTERNATIVELY: you could 1) "FillRect()" the background, then 2) blit the image at a slighly smaller height/width than the background. – paulsm4 Sep 05 '12 at 00:33
  • @RetiredNinja how would I copy it on top? Would you mind showing me? – mugetsu Sep 05 '12 at 00:36

6 Answers6

3

You haven't mentioned the platform, but if you're on x86, Intel's Integrated Performance Primitives (IPP) library, specifically its image processing library, will add borders around images of a chosen color, either in-place on an existing image, or as a part of a copy, resize, or several other operations. And it's extremely efficient code.

vercellop
  • 553
  • 5
  • 18
  • Suggesting a library for something so trivial is just lazy. Why would I add a library to perform some simple pointer arithmetic for me? – Ed S. Sep 05 '12 at 02:33
  • 1
    @EdS.: existing libraries are likely already well-debugged and faster than re-inventing the wheel. The first few (and perhaps all) of the suggested pointer arithmetic solutions here so far have bugs in them. If adding the library dependency is not too costly to the user, it can make sense to pull in such a library. If it is too costly, than something along the lines of your answer is good. – Mr Fooz Sep 05 '12 at 02:37
  • @MrFooz: Simple pointer arithmetic is not "reinventing the wheel". This is extremely basic stuff. And please point out the bug in my example. If you can't handle this in C or C++ then you have some practicing to do. – Ed S. Sep 05 '12 at 03:23
2

Here’s the most readable way I can think of, which is also reasonably efficient. Say you have an image of dimensions Width by Height and desired margins of Left, Right, Top, and Bottom. Allocate a buffer of Width + Left + Right by Height + Top + Bottom filled with zeros. In C++ you can use one of the handy std::vector constructors:

const auto Channels = 3;

const auto TargetWidth = Width + Left + Right;
const auto TargetHeight = Height + Top + Bottom;

std::vector<uint8_t> target(TargetWidth * TargetHeight * Channels);

The C function calloc() is also an option. Next, copy each row in the source image to the target image, starting at vertical offset Top and horizontal offset Left. Use std::copy() to copy rows, and run the outer loop in row-major order to avoid cache misses:

for (int y = 0; y < Height; ++y) {
    const auto* const source_row = &source[y * Width * Channels];
    auto* const target_row = &target[(y + Top) * TargetWidth * Channels + Left];
    std::copy(source_row, source_row + Width * Channels, target_row);
}

If you can use 32-bit RGB0 or RGBA instead of 24-bit RGB, you might see faster copying thanks to more consistent alignment, for which std::copy() or memcpy() are well optimised. If you can use OpenMP, you might also experiment with parallelising the loop:

#pragma omp parallel for
for (int y = 0; y < Height; ++y) {
    // ...
}
Jon Purdy
  • 53,300
  • 8
  • 96
  • 166
  • Don't forget to allocate memory for all channels, or to zero out the border regions (either by using std::fill or by supplying a `0` argument to target's constructor). – Mr Fooz Sep 05 '12 at 02:07
  • @MrFooz: I overlooked the channels, thanks. The `target` constructor is correct as-is, though—the elements are default-constructed. – Jon Purdy Sep 05 '12 at 02:53
  • +1. Thanks for the correction on my default-construction comment (http://stackoverflow.com/q/3803153/25050). – Mr Fooz Sep 05 '12 at 13:30
2

If you're already using an image processing library (Intel Performance Primitives, ImageMagick, Windows, etc.), look for an existing function that already does this. The library implementation will likely be faster if the authors have taken the trouble to use MMX or SSE instructions.

Failing that, you can roll your own using a solution similar to Ed S.'s. Here's a sketch. Additional micro-optimizations like cutting the number of memset calls in half are possible.

unsigned char* padWithBlack(const unsigned char* in,
                            int inWidth, int inHeight,
                            int thickness)
{
    unsigned char* out = malloc((inWidth + thickness * 2) *
                                (inHeight + thickness * 2) * 3);

    // top border
    int topOrBottomOutBytes = (inWidth + thickness * 2) * thickness * 3;
    memset(out, 0, topOrBottomOutBytes);

    // middle section
    unsigned char* inRow  = in;
    unsigned char* outRow = out + topOrBottomOutBytes;
    for (int inY = 0; inY < inHeight; inY++) {
        // left border
        memset(outRow, 0, thickness * 3);
        outRow += thickness * 3;
        // center section
        memcpy(outRow, inRow, inWidth * 3);
        outRow += inWidth * 3;
        inRow  += inWidth * 3;
        // right border
        memset(outRow, 0, thickness * 3);
        outRow += thickness * 3;
    }

    // bottom border
    memset(out, 0, topOrBottomOutBytes);
}
Mr Fooz
  • 109,094
  • 6
  • 73
  • 101
0

Have you looked at any pre-built image processing libraries? I've used ImageMagick in the past - not the easiest thing to install, but it can do a lot once you get it working. Including exactly what you want.

And if you really need to have your program doing the image manipulations, it's open source. Though we're talking "more than 400,000 lines of C code", so you'll probably want to avoid touching that if you can possibly avoid it.

Hope that helps!

Xavier Holt
  • 14,471
  • 4
  • 43
  • 56
0

One consideration is that the resulting image has to be larger than the source, unless the border overwrites the original. The latter case just involves overwriting the pixels in question, however the 'larger output' case will require first allocating space for a new image. Once you allocate the new one (likely through some library that generates the appropriate headers), you'll need to either memcopy the old one in line by line (to account for the gaps), use a fast api that copies rectangular swatches like bitblt, or use a library that does essentially the same thing. The method will depend on the image format itself; simple bits[width*height-1] = {0}; formats like bitmaps, will work with the line-by-line memcpy or bit blt, but compressed formats like pngs might need a library that can interpret the data structures. If your environment already has the libraries (eg MSVS or Qt), you may as well just use that. But if that isn't an option you can just use a 3rd party library like those recommended by the other posters.

Ghost2
  • 536
  • 3
  • 13
-1

So... I'm not going to suggest using a library for something so simple. If you need to perform more advanced operations in the future definitely look into it, but to paint a border? No way.

This is some code I have lying around. It draws a black rectangle at a given location. You can easily change it to only color in the edges if you like. Note that it assumes a 24 bpp image.

void WriteDebugImage( const unsigned char* inImageBuf, int inImageWidth, int inImageHeight, int left, int top, int right, int bottom, unsigned char* outImage )
{   
    // copy the old image into the new one
    memcpy( outImage, inImageBuf, inImageWidth * inImageHeight * 3 );
    for( int x = left; x <= right; ++x )
    {
        outImage[3 * top * inImageWidth + 3 * x]     = 0;    //Top Edge
        outImage[3 * top * inImageWidth + 3 * x + 1] = 0;    //Top Edge
        outImage[3 * top * inImageWidth + 3 * x + 2] = 0;    //Top Edge

        outImage[3 * bottom * inImageWidth + 3 * x]     = 0; //Bottom Edge
        outImage[3 * bottom * inImageWidth + 3 * x + 1] = 0; //Bottom Edge
        outImage[3 * bottom * inImageWidth + 3 * x + 2] = 0; //Bottom Edge
    }

    for (int y = top; y <= bottom; ++y)
    {
        outImage[3 * y * inImageWidth + 3 * left]     = 0;  //Left Edge
        outImage[3 * y * inImageWidth + 3 * left + 1] = 0;  //Left Edge
        outImage[3 * y * inImageWidth + 3 * left + 2] = 0;  //Left Edge

        outImage[3 * y * inImageWidth + 3 * right]     = 0;  //Right Edge
        outImage[3 * y * inImageWidth + 3 * right + 1] = 0;  //Right Edge
        outImage[3 * y * inImageWidth + 3 * right + 2] = 0;  //Right Edge
    }
}
Ed S.
  • 122,712
  • 22
  • 185
  • 265
  • You'll need a memcpy for each row since a left and right column are being inserted. – Mr Fooz Sep 05 '12 at 01:40
  • @MrFooz: No you don't; it takes an input buffer and rectangle boundaries as parameters. It could easily be changed to take an input width and height as well. I was on my way out of work and didn't have the time to modify it myself, but I made that clear. – Ed S. Sep 05 '12 at 03:25
  • The question asks how to create a new larger image with a border on it. It does not ask how to make a same-size copy of an image and render a rectangle on it, which your solution does indeed implement correctly and cleanly. – Mr Fooz Sep 05 '12 at 13:39
  • @MrFooz: I know. Like I said, it was just an example of how to draw a rectangle and I didn't have time to adjust it for him. I have some time today, I should update it. – Ed S. Sep 05 '12 at 17:29