I think there are too many implicit assumptions inside the code that you should verify before making use of them. For example you are just using GetImageVPA
on the first plane - you don’t verify whether the increments in x
and y
are linear and the same for all planes, you don’t verify whether there are padding bytes somehwere, you don’t verify whether the increment from plane to plane is identical for all planes - and if just one of these assumptions is not met by the block of memory you are dealing with, then an access violation is the inevitable consequence (that’s in fact one of the downsides of CreateImageFromPointer
- you can easily end up with an access violation…
Please consider this little project (sorry for the unimaginative name ):ConsoleApplication25.zip (5.8 KB)
To make sure we have something that we can work with, the main()
routine generates an image with defined (and benign) memory layout:
void __stdcall nop_release(void*, void*) {}
int main()
{
static const int w = 128;
static const int h = 192;
static const int dim = 256;
// generate a test image where each pixel of a given plane has been set to
// the plane number; if we swap y <-> z we should see a grey wedge
std::vector<uint8_t> buffer(w * h * dim);
for (int x = 0; x < w; ++x)
for (int y = 0; y < h; ++y)
for (int z = 0; z < dim; ++z)
{
int cell = x + w * y + w * h * z;
buffer[cell] = z;
}
IMG imgBuffer = nullptr;
CreateImageFromPointer(buffer.data(), buffer.size(), w, h, dim, 8, 1, w, w * h, nullptr, nop_release, nullptr, imgBuffer);
// for debugging: WriteImageFile(imgBuffer, "D:\\test.mio");
try
{
auto imgTransposed = TransposeImage(imgBuffer);
// for debugging: WriteImageFile(imgTransposed, "D:\\transposed.mio");
ReleaseObject(imgTransposed);
}
catch (std::exception& e)
{
std::cout << e.what() << std::endl;
}
ReleaseObject(imgBuffer);
return 0;
}
With the TransposeImage()
function invoked in the try/catch
block implemented like this:
IMG TransposeImage(IMG imgOriginal)
{
// assumptions:
// imgOriginal is an IMG handle to the x-y-planes arranged input image
// - for simplicity it is assumed that there are no padding bytes in any
// direction and that the pixel at coordinate (0,0,0) lies at the lowest
// address...
auto w = ImageWidth(imgOriginal);
auto h = ImageHeight(imgOriginal);
auto dim = ImageDimension(imgOriginal);
// determine the increments and check whether linearity expectations
// are met
intptr_t xInc = 0;
intptr_t yInc = 0;
intptr_t zInc = 0;
intptr_t pBasePrev = 0;
intptr_t pBase = 0;
intptr_t pBaseOut = 0;
if ((dim > 1) && (!GetLinearAccess(imgOriginal, 1, reinterpret_cast<void**>(&pBase), &xInc, &yInc)))
throw std::exception(ErrNotAllPlanesLinear);
if (!GetLinearAccess(imgOriginal, 0, reinterpret_cast<void**>(&pBasePrev), &xInc, &yInc))
throw std::exception(ErrNotAllPlanesLinear);
if (dim > 1)
zInc = pBase - pBasePrev;
pBaseOut = pBasePrev;
for (int i = 1; i < dim; ++i)
{
intptr_t xIncTmp = 0;
intptr_t yIncTmp = 0;
if (!GetLinearAccess(imgOriginal, i, reinterpret_cast<void**>(&pBase), &xIncTmp, &yIncTmp))
throw std::exception(ErrNotAllPlanesLinear);
if ((xIncTmp != xInc) || (yIncTmp != yInc))
throw std::exception(ErrVariableIncrements);
auto zIncTmp = pBase - pBasePrev;
if (zIncTmp != zInc)
throw std::exception(ErrVariableIncrements);
std::swap(pBase, pBasePrev);
}
// now build a new image that shares the memory of the input image but
// has a transposed VPAT view on that image
IMG retval = nullptr;
if (SUCCEEDED(CreateImageFromPointer(reinterpret_cast<void*>(pBaseOut), w * h * dim, w, dim, h, 8, xInc, zInc, yInc, nullptr, TransposedRelease, imgOriginal, retval)))
ShareObject(imgOriginal);
return retval;
}
The result looks as expected and no access violation occurs. Please mind that there are still two assumptions made and not verified by that code (see initial comment in TransposeImage()
:
- Pixel (0,0) on plane 0 lies at the lowest address.
- no padding bytes have to be taken into account.
It should be fairly easy to extend this example to pixels with more than 8 bits per pixel (simply add * sizeof(<pixeltype>)
where appropriate). Adapting to padding bytes should also be straightforward (in fact, now that I think about it, the code might already work in the presence of padding bytes).
The following advice should be dealt with care; it requires a firm grasp of pointer arithmetics and a superhuman amount of patience to implement and debug! You have been warned…
Adaption to a situation where the lowest address is at some more or less random coordinate or planes are not contiguous is a lot more difficult. If you have to deal with such a slightly weird layout then the first thing to investigate is whether or not the memory layout is at all compatible with the VPAT model used by :cvb:. For that to be the case the x offset must be independent of the y coordinate in the transposed image and the y offset must be independent of the x coordinate in the transposed image. If that requirement is met, you can create the result image with CreateImageFromPointer
(and latch its lifetime to that of the input image as shown in my sample project) and then manually overwrite the VPATs of all planes as needed (simply retrieve the VPAT pointers via GetImageVPA
and write to the tables - the written data ends up inside the image’s VPAT and the changes will persist).
But be warned: Implementing and debugging something like that can be extremely painful and frustrating. In fact, if your memory layout is not compatible with the assumptions made in the sample code I sent you, you might be better off rearranging the data in memory…