|
Finally, someone else has discovered the use of GetBits()! I found this some time ago and was thinking of writing an article too, because I couldn't understand why everyone was still using GetPixel() and complaining about how slow it is. So I'm glad you wrote something on this.
Just an aside, to make image manipulations even faster and for easier coding, after I load the image file I convert it to a top-down DIB. This way your image manipulation functions (that modify all pixels) only need one loop that iterates GetWidth*GetHeight times. Also, of course, don't put a call to GetWidth() and GetHeight() in a loop to call repeatedly, use variables to hold the values.
To convert to top-down, just copy all the pixels into a temp buffer using GetPitch() or GetPixelAddress(), do a .Destroy() call to release the CImage, then do a .Create(...) call with a negative height value to make the new top-down image, and copy the temp buffer into it with a call to memcpy_s(...). I also make it 32 bit so the GetBits() pointer can be an int.
Manipulations are so fast I use slider buttons in some functions to modify the image and watch the image display change in near real-time.
Thanks,
Darryl
|
|
|
|
|
|
Ial come at your place and kill ur cat LUL.
|
|
|
|
|
How about Bitmap::LockBits method?
Seems it is faster
geoyar
|
|
|
|
|
Sounds like it gets the pointer to some color table. Shouldn't bring great difference to the entire program since it is required to be called once.
|
|
|
|
|
It do brings a difference.
It transfers an area of the bitmap to the memory. Actually it gives to you a memory array. And reading/writing from/to memory is faster (so people are saying.)
In Gdiplus it is
Status Bitmap::LockBits(const Rect *rect,
UINT flags,
PixelFormat format,
BitmapData *lockedBitmapData
);
You can ask for any pixel format legal in Gdiplus .
In MFC it is
DWORD CBitmap::GetBitmapBits(
DWORD dwCount,
LPVOID lpBits
) const;
Copies the bit pattern of the CBitmap object into the buffer that is pointed to by lpBits
It is less useful, but again, after calling it you are facing PC memory.
I think something like it exists in Win SDK too.
geoyar
|
|
|
|
|
The cpu caching algorithms are optimised for sequential use. It's very complicated. Take my word for it.
Your loops will almost always be much faster if the outer loop is vertical, and the inner loop horizontal, so that the inner loop accesses memory sequentially
e.g.:
for (int y = 0; y < height; ++y)
for (int x = 0; x < width; ++x)
{
//do something with x,y
}
or:
for (int y = 0; y < height; ++y)
{
p = startOfLine(y);
for (int x = 0; x < width; ++x)
{
//do something with *p
p = nextPixel(p); // or p+=bytesPerPixel if 1==sizeof(*p), or ++p if bytesPerPixel==sizeof(*p)
}
}
making width and height variables will sometimes allow the compiler to generate better code
|
|
|
|
|
|
Geez thnx for the advice.
|
|
|
|
|
By the way in my previous failed job of video stream processing I did the vertical image processing since it required to mix pixel colors with the couple of bottom pixlz. But the resulting CPU load was 2x25% for double blitting and 1x12% for image processing = 62%. My client wants 10%.
|
|
|
|
|
Since it helps newps like me and they send me letterz with thnxez ^_^. Thank you.
|
|
|
|
|
Please could you indicate how much quicker doing direct arithmetic is over using the routines (e.g. by timing conversions of large bitmaps) so that people can see what an improvement your technique has.
Does your method work with compressed bitmaps?
|
|
|
|
|
Hi, it's approx 1000 times faster than GetPixel and SetPixel crap. Unfortunately I have no idea how to deal with JPEGs and stuff, but I am sure there are fast API functions to decompress a JPG into bitmap and then deal with color table. If you find some please can you post them I shall add them to teh article laterz. THNX
By the way the code and the project attached is just an example to compare these two methods. If you build it and launch in conjunction with a C:\\1.bmp file (of large size) you can see by yourself the difference. At my computer (3GHZ P4) an image of 800x600 was processed through GetPixel and SetPixel for 3seconds. And through direct arithmetics it was done for 0.01 seconds or so.
|
|
|
|
|
I think you would struggle with JPEG, but PNG and compressed BMP / DIB is doable. I don't use C++ so am unsure of what the CImage class gives you.
I started writing a JavaScript module for dynamic creation / editing of BMPs, but other tasks have pushed it down my stack of things to do so it is incomplete and not fully debugged. FWIW, here is the extract from it for handling BMPs, including compressed ones (you can get the BMP file structure and then convert that into C++ syntax):
function Picture.prototype.fromBMP(text)
{
if (text.length == 0)
return this.error.raise(Picture.E_FROMBMP_NOTEXT);
else if ( text.substring(0, 2) != 'BM'
|| text.toNumber4R(2) <= 54
|| text.length != text.toNumber4R(2)
)
return this.error.raise(Picture.E_FROMBMP_BADSIGNATURE);
var width = text.toNumber4R(18);
var height = text.toNumber4R(22);
var depth = text.toNumber2R(28);
var compression = text.charCodeAt(30);
var imageMapLength = text.toNumber4R(34);
var plen = text.toNumber4R(46);
if (plen == 0 && depth < 24)
plen = 1 << depth;
var plen4 = plen * 4;
var imageMap = text.substring(54 + plen4);
var palette = new Array();
for (var i = 0; i < plen4; i += 4)
palette[i >> 2] = text.toNumber3R(54 + i);
this.canvas = new Array();
this.crop(1, width, 1, height);
this.moveTo(1, 1);
this.foregroundColour = plen < 1 ? this.foregroundColour : palette[0];
this.backgroundColour = plen < 2 ? this.backgroundColour : palette[1];
switch (depth)
{
case 1:
var paddedWidth = ((width + 31) >> 5) << 2;
for (var row = 0; row < height; row++)
{
var rowStartOffset = row * paddedWidth;
for (var col = 0; col < width; col += 8)
{
var pByte = imageMap.charCodeAt(rowStartOffset + col / 8);
for (var i = 0; i < 8; i++)
if ((pByte & (1 << i)) == 0)
this.pixel(col + 8 - i, row + 1, this.foregroundColour);
}
}
break;
case 4:
if (compression)
{
var row = 1;
var col = 1;
var ix = 0;
while (ix < imageMap.length)
{
var i = imageMap.charCodeAt(ix++);
if (i == 00)
{
i = imageMap.charCodeAt(ix++);
switch (i)
{
case 00:
col = 0;
row++;
break;
case 01:
ix = imageMap.length;
break;
case 02:
row += imageMap.charCodeAt(ix++);
col += imageMap.charCodeAt(ix++);
break;
default:
for (var j = 0; j < i; j += 2)
{
var pByte = imageMap.charCodeAt(ix + j / 2);
this.pixel(++col, row + 1, palette[pByte >> 4]);
if (j + 1 < i)
this.pixel(++col, row + 1, palette[pByte & 0xF]);
}
ix += (i + 1) > 1;
}
}
else
{
var pByte = imageMap.charCodeAt(ix++);
var a = palette[pByte >> 4];
var b = palette[pByte & 0xF];
for (var j = 0; j < i; j += 2)
{
this.pixel(++col, row + 1, a);
if (j + 1 < i)
this.pixel(++col, row + 1, b);
}
}
}
}
else
{
var paddedWidth = ((width + 7) >> 3) << 2;
for (var row = 0; row < height; row++)
{
var rowStartOffset = row * paddedWidth;
for (var col = 0; col < width; col += 2)
{
var pByte = imageMap.charCodeAt(rowStartOffset + col / 2);
this.pixel(col + 1, row + 1, palette[pByte >> 4]);
this.pixel(col + 2, row + 1, palette[pByte & 0xF]);
}
}
}
break;
case 8:
if (compression)
{
var row = 1;
var col = 1;
var ix = 0;
while (ix < imageMap.length)
{
var i = imageMap.charCodeAt(ix++);
if (i == 00)
{
i = imageMap.charCodeAt(ix++);
switch (i)
{
case 00:
col = 0;
row++;
break;
case 01:
ix = imageMap.length;
break;
case 02:
row += imageMap.charCodeAt(ix++);
col += imageMap.charCodeAt(ix++);
break;
default:
for (var j = 0; j < i; j++)
this.pixel(col++, row, palette[imageMap.charCodeAt(ix++)]);
if (i & 0x1)
ix++;
}
}
else
{
var xx = imageMap.charCodeAt(ix++);
if (xx)
for (var j = 0; j < i; j++)
this.pixel(col++, row, palette[xx]);
else
col += i;
}
}
}
else
{
var paddedWidth = (width + 3) & 0xFFFFFF8;
for (var row = 0; row < height; row++)
{
var rowStartOffset = row * paddedWidth;
for (var col = 0; col < width; col++)
this.pixel(col + 1, row + 1, palette[imageMap.charCodeAt(rowStartOffset + col)]);
}
}
break;
case 24:
for (var row = 0; row < height; row++)
{
var rowStartOffset = row * height * 3;
for (var col = 0; col <= width; col++)
this.pixel(col + 1, row + 1, imageMap.toNumber3R(rowStartOffset + col * 3));
}
break;
default:
this.error.raise(Picture.E_FROMBMP_UNSUPPORTED, width, height, depth, compression, plen, imageMap.length);
}
}
Picture.error(
'E_FROMBMP_NOTEXT',
'Cannot read BMP image: No data found',
'E_FROMBMP_BADSIGNATURE',
'Cannot read BMP image: Image is not in a Windows Bitmap (BMP) format',
'E_FROMBMP_UNSUPPORTED',
'Cannot read BMP image: BMP image format not supported: ' +
'Width = %1, Height = %2, Depth = %3, Compression = %4, ' +
'No of colours = %5, Image map size = %6 chars'
);
function Picture.prototype.toBMP()
{
var pad =
new Function(
"text",
"return text.length % 4 == 0 " +
"? text " +
": text + '\\0\\0\\0\\0'.substring(text.length % 4); "
);
this.palette = new Array(this.foregroundColour, this.backgroundColour);
this.palette.maxLen = 256;
this.palette.findNearest = false;
var bgColour24 = this.backgroundColour.toBinary3R();
var c = this.crop();
var imageMap24 = '';
var imageMap8 = '';
var imageMap1 = '';
var emptyRowMap24 = pad(bgColour24.repeat(this.width));
var emptyRowMap8 = Picture.toBMP_RLE8_compressRepeated('\x01', this.width) + '\0\0';
var emptyRowMap1 = pad('\xFF'.repeat((this.width + 7) >> 3));
for (var row = c.minY; row <= c.maxY; row++)
{
var crow = this.canvas[row];
var plen = this.palette.length;
if (crow && crow.minX <= c.maxX && crow.maxX >= c.minX)
{
var firstPixel = crow.minX < c.minX ? c.minX : crow.minX;
var lastPixel = crow.maxX > c.maxX ? c.maxX : crow.maxX;
var gapLeft = firstPixel - c.minX;
var gapRight = c.maxX - lastPixel;
if (plen > 256)
{
var rowMap24 = '';
for (var col = firstPixel; col <= lastPixel; col++)
rowMap24 +=
typeof crow[col] == 'undefined' ? bgColour24 : crow[col].toBinary3R();
imageMap24 +=
pad(
(gapLeft ? bgColour24.repeat(gapLeft) : '') +
rowMap24 +
(gapRight ? bgColour24.repeat(gapRight) : '')
);
}
else
{
var rowMap24 = '';
var rowMap8 = '';
var rowMap1 = '';
var rowMap1BitNo = 7 - (gapLeft & 0x7);
var rowMap1Byte = (0xFF << (rowMap1BitNo + 1)) & 0xFF;
for (var col = firstPixel; col <= lastPixel; col++)
{
var pixel = typeof crow[col] == 'undefined' ? this.backgroundColour : crow[col];
rowMap24 += pixel.toBinary3R();
var index = this.paletteIndex(pixel);
rowMap8 += String.fromCharCode(index);
if (plen <= 2)
if (rowMap1BitNo)
rowMap1Byte |= index << rowMap1BitNo--;
else
{
rowMap1 += String.fromCharCode(rowMap1Byte + index);
rowMap1Byte = 0;
rowMap1BitNo = 7;
}
}
imageMap24 +=
pad(
(gapLeft ? bgColour24.repeat(gapLeft) : '') +
rowMap24 +
(gapRight ? bgColour24.repeat(gapRight) : '')
);
imageMap8 +=
(gapLeft ? Picture.toBMP_RLE8_compressRepeated('\x01', gapLeft) : '') +
Picture.toBMP_RLE8_compress(rowMap8) +
(gapRight ? Picture.toBMP_RLE8_compressRepeated('\x01', gapRight) : '') +
'\0\0';
if (plen <= 2)
{
while (gapRight)
if (rowMap1BitNo)
{
rowMap1Byte |= 0x01 << rowMap1BitNo--;
gapRight--;
}
else
{
rowMap1 += String.fromCharCode(rowMap1Byte + 0x01);
gapRight--;
rowMap1Byte = 0;
rowMap1BitNo = 7;
if (gapRight >= 8)
{
rowMap1 += '\xFF'.repeat(gapRight >> 3);
gapRight &= 0x7;
}
}
imageMap1 +=
pad(
(gapLeft > 7 ? '\xFF'.repeat(gapLeft >> 3) : '') +
rowMap1 +
(rowMap1BitNo == 7 ? '' : String.fromCharCode(rowMap1Byte))
);
}
}
}
else
{
imageMap24 += emptyRowMap24;
if (plen <= 256)
{
imageMap8 += emptyRowMap8;
if (plen <= 2)
imageMap1 += emptyRowMap1;
}
}
}
var paletteMap = '';
var plen = this.palette.length;
var depth = plen > 256 ? 24 : (plen > 2 ? 8 : 1);
if (plen <= 256)
{
imageMap24 = depth == 8 ? imageMap8 : imageMap1;
for (var i = 0; i < plen; i++)
paletteMap += (this.palette[i] & 0x00FFFFFF).toBinary4R();
}
return (
'BM' +
(14 + 40 + paletteMap.length + imageMap24.length).toBinary4R() +
'\0\0\0\0' +
(14 + 40 + paletteMap.length).toBinary4R() +
'\50\0\0\0' +
this.width.toBinary4R() +
this.height.toBinary4R() +
"\1\0" +
depth.toBinary2R() +
(depth == 8 ? '\1\0\0\0' : '\0\0\0\0') +
imageMap24.length.toBinary4R() +
'\0\0\0\0' +
'\0\0\0\0' +
(paletteMap.length / 4).toBinary4R() +
'\0\0\0\0' +
paletteMap +
imageMap24
);
}
function Picture.toBMP_RLE8_compress(text)
{
var result = '';
if (text)
{
var prevch = text.charAt(0);
var repeatStart = 0;
var segStart = 0;
var tLen = text.length;
for (var i = 1; i < tLen; i++)
{
var ch = text.charAt(i);
if (ch != prevch)
{
if (i > repeatStart + 2)
{
result +=
( segStart == repeatStart
? ''
: Picture.toBMP_RLE8_compressSegment(text.substring(segStart, repeatStart))
) +
Picture.toBMP_RLE8_compressRepeated(prevch, i - repeatStart);
segStart = i;
}
prevch = ch;
repeatStart = i;
}
}
if (tLen > repeatStart + 2)
result +=
( segStart == repeatStart
? ''
: Picture.toBMP_RLE8_compressSegment(text.substring(segStart, repeatStart))
) +
Picture.toBMP_RLE8_compressRepeated(prevch, tLen - repeatStart);
else
result += Picture.toBMP_RLE8_compressSegment(text.substring(segStart));
}
return result;
}
function Picture.toBMP_RLE8_compressRepeated(pixel, count)
{
var result = '';
while (count >= 255)
{
result += '\xFF' + pixel;
count -= 255;
}
return result + (count ? String.fromCharCode(count) + pixel : '');
}
function Picture.toBMP_RLE8_compressSegment(text)
{
var result = '';
while (text.length > 254)
{
result += '\x00\0xFE' + text.substring(0, 254);
text = text.substring(254);
}
switch (text.length)
{
case 0:
break;
case 1:
result += '\x01' + text;
break;
case 2:
result += '\x01' + text.charAt(0) + '\x01' + text.charAt(1);
break;
default:
result += '\x00' + String.fromCharCode(text.length) + text + (text.length & 0x1 ? '\x00' : '');
}
return result;
}
String.prototype.toNumber2R =
new Function(
"optStartOffset",
"var i = optStartOffset ? optStartOffset : 0; " +
"return (this.charCodeAt(i + 1) << 8) | this.charCodeAt(i); "
);
String.prototype.toNumber3R =
new Function(
"optStartOffset",
"var i = optStartOffset ? optStartOffset : 0; " +
"return (this.charCodeAt(i + 2) << 16) | (this.charCodeAt(i + 1) << 8) | this.charCodeAt(i); "
);
String.prototype.toNumber4R =
new Function(
"optStartOffset",
"var i = optStartOffset ? optStartOffset : 0; " +
"return (this.charCodeAt(i + 3) << 24) | (this.charCodeAt(i + 2) << 16) | (this.charCodeAt(i + 1) << 8) | this.charCodeAt(i); "
);
Number.prototype.toBinary2R = new Function("return String.fromCharCode(this & 0xFF, (this & 0xFF00) >> 8); ");
Number.prototype.toBinary3R = new Function("return String.fromCharCode(this & 0xFF, (this & 0xFF00) >> 8, (this & 0xFF0000) >> 16); ");
Number.prototype.toBinary4R = new Function("return String.fromCharCode(this & 0xFF, (this & 0xFF00) >> 8, (this & 0xFF0000) >> 16, (this & 0xFF000000) >>> 24); ");
|
|
|
|
|
looks like this code creates an image of requested format.
I wish I could know JavaScript as kewl as you do...
You see, I used windows API functions that worked slow. Then I used ptr arithmetics. You use browser functions (that is thru javascript). I don't even have an idea how to use pointers (to computer's memory) in javascript. If there is that way, I think I'd be able to help you.
By the way if you use images to allow people draw with pallete or pencil tools you shouldn't experience performance troubles unless some stupid bug in your code.
|
|
|
|
|
Phew, some harsh commenting!
Although I have used a similar technique myself, I still believe it is worthy of being read by people who are new to programming/imaging.
Thank you for the effort! 3/5 from me.
|
|
|
|
|
Yupp I also think so. Maybe far not an article but I hope google shall scan it and tell people a solution on "Omg why my graphic program is so freaking slow?".
PS: First pancake is always a roll hehe.
|
|
|
|
|
Two things:
As others have already pointed out, this is not an article, but rather a code dump. An article would be, for example, an explanation of how the bitmap is stored, and how to access its pixels directly, with some example code. But a code listing does not make an article.
Second, and more important, your code is wrong. You are accessing the pixels as if they were stored as RGB, but they are stored the other way around, as BGR. The example you provide seems to work because you are changing the green channel, which is in the middle in both cases, but if you change the Red or Blue channels, you'll get different results with the two methods.
For an example of use of the same technique, with the same error, by the way, in codeproject: Converting Color to Grayscale Using ATL::CImage[^]
|
|
|
|
|
Hey mluri thank for your comment, you are right. I forgot to say that ptr+0 is a B color, ptr+1 is a G color, ptr+2 is an R color. Thank you for your help, I hope people shall read your post or find out by themselves bout that.
modified on Tuesday, June 8, 2010 4:37 AM
|
|
|
|
|
Well sorry that's my first time, I've got screwed with the javascriptz text editor since it put large gaps between lines instead of posting plain text. I shall pay more attention previewing and modyfying the result next time.
Regarding dummies, I spent couple of weeks searching for solutions but all I had were just lockbits and DrawDibs functions without any physical sense of the problem. And the sense is how the bitmap data is located. Microsoft made it hidden within HDCs and HBITMAPS. That's what I explained in this so called article.
And by the way I don't need any code to be posted as a reply, this stuff works well and It was written as an example to show the difference between GetPixels-like API functions and the pointer arithmetics method. I doubt that there can be a practical use of this code (not the method).
And by the way Opera browser does not support the creation of articles but the formatting is well.
PS: I am trying to get close to people who knows programming well for more than 2years but noone in our damned land has any idea bout programming and noone can teach us. Just few of us speak english. Most stuff we learned from MSDN and codeproject, however the method I described I couldn't find anywhere including this site. Maybe coz it is "for dummies", but a lor of such the articles I saw here. And they helped me a lot. I hope my so called article shall help someone too.
modified on Saturday, June 5, 2010 9:22 AM
|
|
|
|
|
If you want to see how this is really done, search the articles for "Image processing for dummies".
You really haven't written an article. You've supplied a code dump with the hope that comments in the code will write it for you. That never works.
|
|
|
|
|
Thank you, sir, coz it's my first article here. Now I kno how to fix it.
|
|
|
|
|
0) Your title is too long
1) Your formatting is screwed up
2) It's just a code dump
3) It appears as if this would make a better tip/trick than it does an article.
.45 ACP - because shooting twice is just silly ----- "Why don't you tie a kerosene-soaked rag around your ankles so the ants won't climb up and eat your candy ass..." - Dale Earnhardt, 1997 ----- "The staggering layers of obscenity in your statement make it a work of art on so many levels." - J. Jystad, 2001
|
|
|
|
|