I’ve got some code that accesses the virtual pixel access table, and prints the values to a csv file. This works perfectly well for any number of planes, but only up to eight bit. I assume this is because I’m using byte pointers. However, after increasing to short pointers, so that I can work with bit depths up to 16, the pixel values I’m reading are now random, and include negative numbers.
I then tried using unsigned short pointers, and then I only seem to get every other pixel (horizontally).
Considering the symptoms that you describe I suspect that you have converted one pointer too many to unsigned short… Consider the following snippet:
1 unsigned char *base = 0;
2 PVPAT vpa = 0;
3 GetImageVPA(img, 0, reinterpret_cast<void**>(&base), &vpa);
4 // loops over all lines and columns...
5 for (y = 0 ; y < height ; ++y)
6 {
7 // precalculated line pointer saves time...
8 unsigned char* line = base + vpa[y].YEntry;
9 for (x = 0 ; x < width ; ++x)
10 {
11 auto pPix = reinterpret_cast<unsigned char*>(line + vpa[x].XEntry);
12 // pPix points to the pixel now! do with it whatever pleases you...
13 ...
14 }
15 }
As it uses unsigned char* it’s evident that this is suitable for 8 bits per pixel only. You might be tempted to simply substitute all the unsigned char* with unsigned short* to make that bit of code 16 bit capable - but that’s wrong:
changing the type in line 1 will most likely make your code skip every other line (the exact behavior depends on the actual content of the VPAT)
changing the type in line 8 will most likely make your code skip every other pixel (again, this ultimately depends on the actual content of the VPAT)
There is exactly one line where you need to make that substitution: Line 11 where the reinterpret_cast happens - all the other parts need to continue using unsigned char*: The VPAT gives byte offsets; therefore for pointer arithmetics to work properly it must be carried out with a 1-byte type regardless of the pixel type. Only once you calculated the address of the pixel is it necessary to change the interpretation of that pointer. So for 16 bits per pixel the code should look like this:
1 unsigned char *base = 0;
2 PVPAT vpa = 0;
3 GetImageVPA(img, 0, reinterpret_cast<void**>(&base), &vpa);
4 // loops over all lines and columns...
5 for (y = 0 ; y < height ; ++y)
6 {
7 // precalculated line pointer saves time...
8 unsigned char* line = base + vpa[y].YEntry;
9 for (x = 0 ; x < width ; ++x)
10 {
11 auto pPix = reinterpret_cast<unsigned short*>(line + vpa[x].XEntry);
12 // pPix points to the pixel now! do with it whatever pleases you...
13 ...
14 }
15 }
Confusing? If so, then try the following twist which (in my opinion) makes the code slightly more obvious because it emphasizes the difference between pixel address calculation and the pixel access. It’s also a tiny little bit safer because you cannot accidentally dereference a base or line pointer:
1 intptr_t base = 0;
2 PVPAT vpa = 0;
3 GetImageVPA(img, 0, reinterpret_cast<void**>(&base), &vpa);
4 // loops over all lines and columns...
5 for (y = 0 ; y < height ; ++y)
6 {
7 // precalculated line pointer saves time...
8 auto line = base + vpa[y].YEntry;
9 for (x = 0 ; x < width ; ++x)
10 {
11 auto pPix = reinterpret_cast<unsigned short*>(line + vpa[x].XEntry);
12 // pPix points to the pixel now! do with it whatever pleases you...
13 ...
14 }
15 }
Thank you very much Illusive! You’ve hit the nail on the head, I was indeed missing a ushort* cast on my equivalent of line 11. With all these pointers it can get confusing, as I was attempting to cast when I shouldn’t have, like you said. It’s working perfectly now.
A quick final question, if I were to theoretically have an image with 64bit depth, casting to an unsigned long pointer would work just fine right?
Edit: Additionally, other than simplicity of coding, is there a reason to use linear access over VPAT to access image pixels?
Concerning the 64 bit case I’d recommend using an uint64_t (hoping your compiler is recent enough for this) because unsigned long is ambiguous: The gcc is interpreting this as an unsigned 64 bit integer type, while msvc considers unsigned long to be a 32 bit unsigned integer type…
As for VPAT access versus linear access: The main reason to choose linear access over VPAT access is speed. If linear access is possible loops for accessing all pixels of an image (like in my previous post) can be processed more efficiently because the calculation of the address is simply done by adding a fixed increment (as opposed to a table lookup plus addition in the VPAT case) and the CPU cache is more likely to preemptively cache the correct addresses with that simple access pattern.
Thanks @illusive, I’m still learning how to use this website, so I didn’t know answers could be marked as “the solution”, I’ve done that now.
Thank you also for answering my additional questions, I suppose then it makes sense to implement both methods, and use linear access if available, and only use VPAT in the case it’s not.
Unfortunately, the answer is yes, and that’s what we do in many cases in :cvb: functions. VPAT access is by and large the most versatile way of accessing image data, but (as pointed out earlier) by far not the fastest. On the other hand, code that relies on a linear arrangement of the pixel data (i.e. GetLinearAccess returns true when called on the image) may sooner or later run into a situation where this assumption is violated and fail.
As a rule of thumb: If you are working directly on image data delivered by your camera or frame grabber, then you’re not very likely to see the linear accessibility assumption violated as practically all relevant images sources yield images with linear data arrangement (prominent exception is the RotateImage ini file setting of many :cvb: drivers - see here). As soon as you start working on preprocessed images you should be more alert and at least make sure that your code doesn’t explode in your customer’s face when confronted with nonlinear VPATs.