PDA

View Full Version : How to use "SAFEARRAY of Byte in a Variant"



sapbucket
November 26, 2004, 08:20:44
Hello,

The ICImagingControl.MemoryGetImageData Method returns a "SAFEARRAY of Byte in a Variant". Furthermore, a VB6 example demonstrates how to get/set data:

Private Sub cmdStart_Click()
Dim ImageData As Variant
Dim x As Integer, y As Integer
ICImagingControl1.MemorySnapImage
ImageData = ICImagingControl1.MemoryGetImageData
For y = 0 To ICImagingControl1.ImageHeight - 1
For x = 0 To ICImagingControl1.ImageWidth - 1
ImageData(x, y) = 255 - ImageData(x, y)
Next x
Next y
ICImagingControl1.MemoryReleaseImageData ImageData
ICImagingControl1.Display
End Sub


I am not terribly familiar with VB, but the line ImageData(x,y) seems weird to me. VB6 must have great built-in support for the VARIANT type?

Anyway, my question is how can I get/set image data (just like in the example above) but using C++? or C? or even PERL?

Can someone show me an example (rewrite the above example) using C++ or PERL?

Stefan Geissler
November 26, 2004, 10:59:08
Hello,

I think, i answered yout request just a few minutes before via support@imagingcontrol.com

sapbucket
November 27, 2004, 03:12:29
Stefan

I am having a problem with MemoryGetImageData().

For example:

use Win32::OLE;
$imaging = Win32::OLE->new('IC.ICImagingControl') or die "oops\n";
$imaging->LoadDeviceStateFromFile("c:\\ocr\\device_state.xml",1);
$imaging->MemorySnapImage();
$imaging->MemorySaveImage("c:\\ocr\\test.bmp");
$imagedata=$imaging->MemoryGetImageData();
print $imaging->ImageWidth();print ", ";
print $imaging->ImageHeight();print "\n";

$counter=0;
foreach(@$imagedata) {
$counter++;
$size=length($_);
print $_." - (size is $size) - $counter\n";
for($i=0;$i<16;$i++) {
print $_[$i];
}
}
print "image data: [$imagedata]\n";


So, the output of this is $imagedata (image data: [ARRAY(0x19a7e64)]) of type ARRAY.

The bottom portion of the code prints out the $imagedata array. This looks like:

C:\OCR\Dev>perl testActiveX.pl
640, 480
ARRAY(0x19a7e34) - (size is 16) - 1
ARRAY(0x19a94cc) - (size is 16) - 2
ARRAY(0x19aab64) - (size is 16) - 3
ARRAY(0x1ae7a7c) - (size is 16) - 4
ARRAY(0x1aea024) - (size is 16) - 5
ARRAY(0x1aec7b0) - (size is 16) - 6
ARRAY(0x1aeef84) - (size is 16) - 7
ARRAY(0x1af1734) - (size is 16) - 8
ARRAY(0x1af42c8) - (size is 16) - 9
ARRAY(0x1af6a54) - (size is 16) - 10
...

ARRAY(0x99ad7c0) - (size is 16) - 1910
ARRAY(0x99aff70) - (size is 16) - 1911
ARRAY(0x99b26fc) - (size is 16) - 1912
ARRAY(0x99b4e88) - (size is 16) - 1913
ARRAY(0x99b7638) - (size is 16) - 1914
ARRAY(0x99b9dc4) - (size is 16) - 1915
ARRAY(0x99bc550) - (size is 16) - 1916
ARRAY(0x99bed00) - (size is 16) - 1917
ARRAY(0x99c148c) - (size is 16) - 1918
ARRAY(0x99c3c18) - (size is 16) - 1919
ARRAY(0x99c63c8) - (size is 16) - 1920



So, this is onethousandnineteenhundredandtwenty more arrays, each of length 16. So the dimensions are correct: 640x480=307,200. And if we divide by 16, we get =1,920.

Now, this is where my problem comes in. If I try and print the values of one of the 1,920 arrays (ie print out all 16 values), I get "Use of uninitialized value in print at testActiveX.pl line 46."

This means that the imagedata is all ZEROS or a complete black image. Why is MemoryGetImageData() returning black image???? Is this because the buffer is still in use and I need to wait for something before I get the image data? I am confused. I can't get at the image data, but i see that the image has the correct dimensions.

Stefan Geissler
November 29, 2004, 07:45:50
Hello,

This problem is known from using IC 1.41 in VB.NET. Only one access to the image data was possible. After this first access, the array was empty. Thus we created a correct .NET component of IC Imaging Control.
But i wonder, why you get onethousandnineteenhundredandtwenty single arrays? In the VARIANT structure should be one pointer to the array and some further informationsof the array dimensions.
The VARIANT data type is declared as follows:


typedef struct tagSAFEARRAYBOUND {
unsigned long cElements;
long lLbound;
} SAFEARRAYBOUND;


typedef struct tagSAFEARRAY
{
USHORT cDims;
USHORT fFeatures;
USHORT cbElements;
USHORT cLocks;
USHORT handle;
PVOID pvData;
SAFEARRAYBOUND rgsabound[1];
} SAFEARRAY;


typedef struct tagVARIANT {
WORD vt;
unsigned short wReserved1;
unsigned short wReserved2;
unsigned short wReserved3;
SAFEARRAY FAR* parray;
} VARIANT;

The byte pointer to the image data is stored in parray->pvData. This is a byte stream with imagewidth * imageheigth * bytesperpixel bytes length.
If the sink type has been set to RGB 24 (this is default), the size of the byte stream is 640*480* 3 = 921600.
I think, you must implement the data structure mentioned above and retrieve the pointer to the image data from parray->pvData.

sapbucket
November 29, 2004, 12:23:14
Stefan,

Yes, you are right about the size of the byte stream.

I have access to the image values now!!

However, whenever I attempt to modify the image values, then save the image to file I get a "black" image.

This is what the script looks like now:

#!/usr/bin/perl -w
#use strict;

#---------------------------------
use Win32::GUI;
use Win32::OLE;
#---------------------------------

#---------------------------------
$imaging = Win32::OLE->new('IC.ICImagingControl') or die "oops\n";
$imaging->LoadDeviceStateFromFile("c:\\ocr\\device_state.xml",1);
$imaging->MemorySnapImage();
$imaging->MemorySaveImage("c:\\ocr\\test1.bmp");
#---------------------------------

#---------------------------------
$buffer = $imaging->ImageBuffers->Item(1);
$imagedata=$buffer->GetImageData();

for($h=0;$h<$imaging->ImageHeight();$h++) {
for($w=0;$w<($imaging->ImageBitsPerPixel() / 8)*$imaging->ImageWidth();$w++) {
$$imagedata[$w][$h] = 255 - $$imagedata[$w][$h] ;
if($$imagedata[$w][$h] > 255 || $$imagedata[$w][$h] < 0) {
die "$$imagedata[$w][$h]\n";
}
print $$imagedata[$w][$h].", ";
}
print "\n";

}

$imaging->MemorySaveImage("c:\\ocr\\test2.bmp");

print $imaging->ImageWidth();print ", ";
print $imaging->ImageHeight();print "\n";
#---------------------------------


test1.bmp is the picture that the camera took and looks great, test2.bmp is all BLACK. The dimensions are the same in both.

Here is a sample of the output:

.
.
.
83, 83, 212, 77, 77, 209, 76, 76, 209, 76, 76, 209, 75, 78, 208, 74, 77, 213, 71, 77, 211, 69, 75, 206, 68, 75, 20
8, 70, 77, 207, 69, 76, 206, 68, 75, 213, 67, 75, 213, 67, 75, 210, 67, 73, 211, 68, 74, 206, 70, 73, 206, 70, 73,
210, 68, 70, 210, 68, 70, 210, 68, 70, 211, 69, 71, 206, 68, 69, 205, 67, 68, 210, 66, 68, 209, 65, 67, 212, 63, 66
, 209, 60, 63, 209, 58, 64, 207, 56, 62, 206, 52, 60, 204, 50, 58, 194, 46, 54, 190, 42, 50, 182, 42, 49, 179, 39,
46, 173, 35, 42, 170, 32, 39, 159, 32, 37, 159, 32, 37, 151, 33, 39, 151, 33, 39, 147, 34, 39, 147, 34, 39, 147, 34
, 39, 147, 34, 39, 147, 34, 39, 147, 34, 39, 147, 34, 39, 147, 34, 39, 147, 34, 39, 147, 34, 39, 147, 34, 39, 147,
34, 39, 151, 33, 39, 151, 33, 39, 154, 32, 39, 154, 32, 39, 153, 33, 39, 153, 33, 39, 149, 33, 39, 149, 33, 39, 151
, 33, 39, 151, 33, 39, 151, 33, 39, 151, 33, 39, 151, 33, 39, 151, 33, 39, 154, 32, 39, 154, 32, 39, 154, 32, 39, 1
54, 32, 39, 151, 33, 39, 151, 33, 39, 149, 33, 39, 149, 33, 39, 149, 33, 39, 149, 33, 39, 154, 32, 39, 154, 32, 39,
153, 33, 39, 153, 33, 39, 151, 33, 39, 151, 33, 39, 153, 33, 39, 153, 33, 39, 154, 32, 39, 154, 32, 39, 153, 33, 3
9, 153, 33, 39, 151, 33, 39, 151, 33, 39, 153, 33, 39, 153, 33, 39, 154, 33, 37, 154, 33, 37, 156, 33, 37, 156, 33,
37, 153, 33, 39, 153, 33, 39, 151, 33, 39, 151, 33, 39, 154, 32, 39, 154, 32, 39, 159, 32, 37, 159, 32, 37, 158, 3
2, 37, 158, 32, 37, 158, 32, 37, 158, 32, 37, 156, 33, 37, 156, 33, 37, 154, 32,
.
.
.
640, 480


the above output values are the image values in RGB (4byte per pixel) printed from the nested for loop in the script.

Everything looks like it should be working! The script above should be inverting the image.





I'm glad that I now have the image pixel values! Thanks for your help.

Now I just need to be able ot modify them. If you could be so kind to advise me in how to save the image buffer so that it is not "all black" I would be most happy.


Thanks!

sapbucket

sapbucket
November 29, 2004, 12:28:25
One more thing that strikes me as odd:

When I try:

$buffer->ReleaseImageData($imagedata); after the nested for loops,

I get the following error:

C:\OCR\Dev>perl testActiveX.pl
OLE exception from "IC.ICImagingControl.1":

Invalid argument passed to function

Win32::OLE(0.1701) error 0x80070057: "The parameter is incorrect"
in METHOD/PROPERTYGET "ReleaseImageData" at testActiveX.pl line 55
640, 480



I'm not sure why this is.

Stefan Geissler
November 29, 2004, 13:49:29
Hello,

You should not use GetImageData. This method work only in Visual Basic 6 correctly
You can retrieve a direct pointer to the image data using


$imagedata=$buffer->ImageDataPtr();

The ImageDataPtr() methods returns a long value containing the pointer to the byte data. This is no array. It is just a block of memory. The method ReleaseImageData is not used for this method. I do not know how to handle pointers in PERL, so i am not able to write a sample for you. If you can handle the pointer as array, you could access it as $bytes[i].

sapbucket
November 30, 2004, 05:40:21
This is the result of using ImageDataPtr():

...
45: $buffer = $imaging->ImageBuffers->Item(1);
46: $long = $buffer->ImageDataPtr(); # is now a reference
47: print $long."\n";
48: print $long[0]."\n";
49: $refData = \$long;
50: print $refData."\n";
51: print $refData[0]."\n";
...


OUTPUT:
C:\OCR\Dev>perl testActiveX.pl
145489952
Use of uninitialized value in concatenation (.) or string at testActiveX.pl line 48.

SCALAR(0x19ab680)
Use of uninitialized value in concatenation (.) or string at testActiveX.pl line 51.

640, 480
elapsed time is 1.405944 s
END OUTPUT.


basically, PERL doesn't have support for converting a 'long' type into a pointer. "$refData = \$long;" simply makes a reference to the scalar value returned by ImageDataPtr();.

I have posted in PERL forums asking how to treat this problem. In C, it is very simple to set a pointer using a scalar value as the address location. In PERL, it may not be possible at all.


So far this has been an interesting process. I am curious, has anyone tried to implement the ActiveX control using Java OLE?

sapbucket
November 30, 2004, 06:32:16
Just curious.

Why does ImageDataPtr() return a 'long' type? Why not return an actual pointer?

I have to admit I am stuck. PERL doesn't have the concept of a 'memory block.' Also, I cannot use the scalar 'long' an any meaningful way to reconstruct the image array.

I must be thinking of this all wrong ;)

I wish I could use $imagedata=$buffer->GetImageData();

Stefan Geissler
November 30, 2004, 08:00:31
Hello,

ImageDataPtr () returns a long value, because Visual Basic does not handle pointers. It is just the adress of the first byte of the current image.

In C you would get the contents of the first bytes as follows:


byte *pointer = ImageDataPtr();
&pointer = 255 - &pointer; // Change the content of the byte
pointer++; // get the next byte


I do not know, whether there is a pointer operator in PERL, something like &&.

The IC Imaging Control ActiveX has been written for Visual Basic. Support of other languages is possible, but not guaranteed.

Stefan Geissler
December 8, 2004, 07:07:46
Hello,

Just some additions


Why does ImageDataPtr() return a 'long' type? Why not return an actual pointer?

A value of type long is returned, because a void pointer type is not defined in COM. Even if it would be defined, you would have a problem, if PERL does not support untyped (void) arrays.


I wish I could use $imagedata=$buffer->GetImageData();

COM defines the SAFEARRAY in the way, that applications can pass an own array that is not to be deallocated by COM.
Unfortunately differ COM applications in the methods how to handle these arrays. Some create an own copy, others fill the arrays with 0 at deallocation from stack and some applications just will crash.
Visual Basic 6 handles GetImageData/ReleaseImageData correctly. Most other languages can fail.

sapbucket
December 9, 2004, 03:58:37
Stefan,

I have been exploring the definition of Variant in a VB6 book. A variant is in 16 byte format:

Bytes
----------------
0-1: VarType
2-7: Unused
8-15: Value

While a 'byte' value (the type stored in the array) is a single byte. It should be byte 8 (zero index).

I was wondering why I kept getting size 16 arrays returned.

I havent done it yet, but I am going to try and isolate byte 8 and view it. I am still confused about the formatting because RGB usually has 4 bytes (R,G,B, and O). If EVERY value is stored as 16-byte variant, the following calculation should give the total number of variants and total number of bytes:

640 x 480 x 4 = 1228800 variants x 16 bytes/variant = 19,660,800 bytes.

But that would make the image almost 20 MB! Which doesn't make much sense.


The other idea I had is to look at Bytes 0 and 1. They should be the same for every array (ie, every 16 bytes we should see the same 0-255 value) because the variant uses VarType to encode what the type the variant value is. Since they are all the same type, the byte code should repeat itself. In this way I can at least verify that the Variant is intact and contains the correct value type.

(http://msdn.microsoft.com/library/default.asp?url=/library/en-us/script56/html/vsconvartype.asp)
Constant Value Description
vbEmpty 0 Uninitialized (default)
vbNull 1 Contains no valid data
vbInteger 2 Integer subtype
vbLong 3 Long subtype
vbSingle 4 Single subtype
vbDouble 5 Double subtype
vbCurrency 6 Currency subtype
vbDate 7 Date subtype
vbString 8 String subtype
vbObject 9 Object
vbError 10 Error subtype
vbBoolean 11 Boolean subtype
vbVariant 12 Variant (used only for arrays of variants)
vbDataObject 13 Data access object
vbDecimal 14 Decimal subtype
vbByte 17 Byte subtype
vbArray 8192 Array


Argh...script froze up my IDE...need to reboot.

I will post the the decoded results in a bit



BTW, how do you make those nice 'code' snippets in your posts?

sapbucket
December 9, 2004, 05:36:34
OK,

I have access to the image data.

The array is 1920 x 480 for an rgb image 640x480. This is because there are three bytes per pixel. 640 x 3 = 1920.

Here is my PERL test loop:


$buffer = $imaging->ImageBuffers->Item(1);
$imagedata=$buffer->GetImageData();
for($i=0;$i<@$imagedata;$i++) {
$len=length($$imagedata[$i]);
$refID=$$imagedata[$i];
$index=0;
LOOP:foreach(@$refID) {
if($_ > 25) {
$len2=length($_);
print " ";
print $_." $len2 $index $$imagedata[$i] ($i)\n";
}
}
}


This gives full access to the image data.

Next is manipulating and storing the image.

This dialogue should be quite useful for someone else trying to repeat this process.

Stefan Geissler
December 9, 2004, 08:29:08
Hi,

Congratulations!

The code snippts are in "code" tags.


printf("Hello World");


Press the "Post Reply" button to see possibillites.

sapbucket
December 10, 2004, 01:50:45
Just confirming:



$imagedata=$buffer->GetImageData(); # after this we get black image



after using GetImageData() we get access to the image data BUT any attempt to save the image data does not work (you get a black image).

One might think that the following would work:



$imagedata=$buffer->GetImageData(); # after this we get black image
$buffer->ReleaseImageData($imagedata);
$imaging->MemorySaveImage("c:\\ocr\\test2.bmp");


But it doesn't.

You may use the method explored by Stefan and I for machine vision processes. However, image processing techniques, while possible, are not savable - so you can't "see" what you did to the image. This may not bother some people (such as myself) because I do not need to "see" the image.