I'm working on a (Universal Windows) c++/cx Directx project, which builds to a dll used in a c# UWP project.
I'm using the DirectX Toolkit to load textures.
I already use it to create a texture from file, but now I need it to create a texture from a byte array that was send from the UWP project.
But when trying to use CreateWICTextureFromMemory(), the HRESULT says 0x88982F50:"The component cannot be found"
All I can find about this problem indicates the bytes are not a correct image, but I tested it in the UWP project, there I get the byte array from bingmaps (it's a static map image), and I could make a working image from these bytes.
Does annyone know what I'm doing wrong?
UWP c# download code (to get the bytes):
private async Task DownloadTexture()
{
byte[] buffer = null;
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_url);
WebResponse response = await request.GetResponseAsync();
using (Stream stream = response.GetResponseStream())
using (MemoryStream ms = new MemoryStream())
{
stream.CopyTo(ms);
buffer = ms.ToArray();
}
}
catch (Exception exception)
{
Logger.Error($"Could not Download Texture: {exception}");
}
_track3D.SetImage(out buffer[0], (ulong)buffer.Length);
}
Directx C++ code (that fails):
void Track3D::SetImage(uint8_t* ddsData, size_t ddsDataSize)
{
HRESULT result = CreateWICTextureFromMemory(_d3dDevice.Get(), _d3dContext.Get(), ddsData, ddsDataSize, nullptr, _Terrain.ReleaseAndGetAddressOf());
//here it goes wrong
//TODO: use the texture
}
UWP C# test code that works (displays image):
private async void setImage(byte[] buffer) //test
{
try
{
BitmapImage bmpImage = new BitmapImage();
using (InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream())
{
await stream.WriteAsync(buffer.AsBuffer());
stream.Seek(0);
await bmpImage.SetSourceAsync(stream);
}
Image image = new Image();
image.Source = bmpImage;
((Grid)Content).Children.Add(image);
}
catch (Exception exception)
{
Logger.Error($"{exception}");
}
}
EDIT:
OK, turns out the first byte in the buffer is different in the C++ code, than it was when sent from UWP. When I change that first byte to the correct value in the C++ code (as a test), the texture is correctly created.
Which raises the question, why did the value of the first byte change?
(Or what did I do wrong?)
As requested, The function setImage() looks like this in c#:
[MethodImpl]
public void __ITrack3DPublicNonVirtuals.SetImage(out byte ddsData, [In] ulong ddsDataSize);
(also, I just realised the parameter names still have 'dds' in their name, sorry about that, will change that in my code as it is misleading)
0x88982F50: “The component cannot be found”
This is WINCODEC_ERR_COMPONENTNOTFOUND which happens whenever WIC can't determine what format codec to use for a file/binary. Your problem is your transfer of the data from managed to native code is wrong.
Your interop method is set to:
[MethodImpl]
public void __ITrack3DPublicNonVirtuals.SetImage(out byte ddsData, [In] ulong ddsDataSize);
With the C++ method signature being:
void Track3D::SetImage(uint8_t* ddsData, size_t ddsDataSize)
Because of the out your first parameter is being passed as a safe array with the length in the first element.
Instead you should use:
SetImage([In] byte ddsData, [In] ulong ddsDataSize); // C#
void Track3D::SetImage(const uint8_t* ddsData, size_t ddsDataSize); // C++.
Related
I'm writing a plugin for unity, and I need to send a texture from ios to unity.
There is a UnitySendMessage function which takes char* as a parameter, but I didn't find a way to convert id<MTLTexture> to char*.
How can I send id<MTLTexture> from ios and receive it in unity?
My current code :
//ios side ...
id<MTLTexture> _texture = CVMetalTextureGetTexture(texture);
UnitySendMessage(CALLBACK_OBJECT, CALLBACK_TEXTURE_READY,_texture);//error
//...
//unity side
private void OnTextureReady(string texture_str)
{
IntPtr texture = new IntPtr(Int32.Parse(texture_str));
int width = 256;
int height = 256;
rawImage.texture = Texture2D.CreateExternalTexture(width, height,
TextureFormat.ARGB32, false, false, texture);
}
iOS plugin documentation says that you can only pass strings using UnitySendMessage.
The workaround would be to create a mapping from string to texture objects in Objective-C side, pass the string key via UnitySendMessage and then retrieve the texture object using a custom DllImport function.
Declare you map:
// class field
{
NSMutableDictionary<NSString *, id<MTLTexture>> _textures;
}
// in constructor
_textures = [NSMutableDictionary new];
// in function code
NSString *textureName = #"cookies";
_textures[textureName] = texture; // save MTLTexture for later
UnitySendMessage(CALLBACK_OBJECT, CALLBACK_TEXTURE_READY, textureName);
On the C# side CreateExternalTexture requires a pointer to a texture object of type IntPtr. To obtain it you can declare a DllImport function that takes a texture name and returns IntPtr:
[DllImport("__Internal")]
static extern IntPtr GetMetalTexturePointerByName(string textureName);
and implement it on the iOS side like so:
return plugin->_textures[textureName];
Not sure if it works though in terms of what CreateExternalTexture expects.
See also this post, a guy is doing something similar (but reverse):
Convert uintptr_t to id<MTLTexture>
I have an EmguCV.Image<Brg, Byte>() instance and another 3rd party API, that requires Stream that represents the image data.
I was able to convert the EmguCV image to the stream and pass it to the 3rd party API using System.Drawing.Bitmap.Save but that is not very efficient.
How to get the stream as afficietnly as possible?
This code works:
var image = new Image<Rgb, byte>("photo.jpg");
var bitmap = image.Bitmap// System.Drawing - this takes 108ms!
using (var ms = new MemoryStream())
{
bitmap.Save(ms, ImageFormat.Bmp); //not very efficient either
ms.Position = 0;
return ImageUtils.load(ms); //the 3rd party API
}
I have tried to create UnmanagedMemoryStream directly from the image:
byte* pointer = (byte*)image.Ptr.ToPointer();
int length = image.Height*image.Width*3;
var unmanagedMemoryStream = new UnmanagedMemoryStream(pointer, length);
but when i try to read from it, it throws an AccessViolationException: Attempted to read or write protected memory.
for (int i = 0; i < length; i++)
{
//throw AccessViolationException at random interation, e.g i==82240, 79936, etc
unmanagedMemoryStream.ReadByte();
}
the length is 90419328 in this case and it shoudl be correct, because it has the same value as image.ManagedArray.Length;
How to get the stream without copying the data?
Ok, there is Bytes property of type byte[] on the Image() so the answer is very easy:
new MemoryStream(image.Bytes)
I looking for the equivalent of
java.io.DataInputStream.readDouble() for Vala.
Is it even possible?
Currently I have :
public double data;
public override void load (DataInputStream dis) throws IOError {
data = dis.read_int64 ();
}
But it just converting a int64 to a double which is not what I want.
I've tried all sort of casting and de-referencing, but nothing seems to work.
This worked for me:
int main()
{
int64 foo = 0; // Whatever value you have
double data = *(double*)(&foo); // This is where the "magic" happens
stdout.printf("%f", data);
return 0;
}
Mind you, you may have to set the correct byte order for the conversion to succeed.
I have seen lots of DataSnap examples in Delphi, but fewer in C++ Builder, and have not figured out how to specify that a TStream should be returned to the calling client.
I am using a simple configuration, similar to the tutorials I have seen. An example server method is:
System::UnicodeString GetData(int PatientID, int& count, TStream* stream);
I have no trouble calling that method from my client. Because count is passed as a reference, the DataSnap server knows to send it back to the client. Generate Client Classes, on the TSQLConnection in the client, connects to the server, and generates the following:
System::UnicodeString __fastcall TServerMethods1Client::GetData(int PatientID, int &count, TStream* stream)
{
if (FGetDataCommand == NULL)
{
FGetDataCommand = FDBXConnection->CreateCommand();
FGetDataCommand->CommandType = TDBXCommandTypes_DSServerMethod;
FGetDataCommand->Text = "TServerMethods1.GetData";
FGetDataCommand->Prepare();
}
FGetDataCommand->Parameters->Parameter[0]->Value->SetInt32(PatientID);
FGetDataCommand->Parameters->Parameter[1]->Value->SetInt32(count);
FGetDataCommand->Parameters->Parameter[2]->Value->SetStream(stream, FInstanceOwner);
FGetDataCommand->ExecuteUpdate();
count = FGetDataCommand->Parameters->Parameter[1]->Value->GetInt32();
System::UnicodeString result = FGetDataCommand->Parameters->Parameter[3]->Value->GetWideString();
return result;
}
One can see that the generated code is setting the count from the returned parameter, indicating that the server is sending it back. However, the stream is only sent to the server, and not set back on the client.
In Delphi, I would use var to indicate that the reference should be passed back to the caller. However, using a reference on TStream does not work, either.
For this definition:
System::UnicodeString GetData(int PatientID, int& count, TStream& stream);
I get this generated code:
System::UnicodeString __fastcall TServerMethods1Client::GetData(int PatientID, int &count, TStream* &stream)
{
if (FGetDataCommand == NULL)
{
FGetDataCommand = FDBXConnection->CreateCommand();
FGetDataCommand->CommandType = TDBXCommandTypes_DSServerMethod;
FGetDataCommand->Text = "TServerMethods1.GetData";
FGetDataCommand->Prepare();
}
FGetDataCommand->Parameters->Parameter[0]->Value->SetInt32(PatientID);
FGetDataCommand->Parameters->Parameter[1]->Value->SetInt32(count);
FGetDataCommand->Parameters->Parameter[2]->Value->SetStream(stream, FInstanceOwner);
FGetDataCommand->ExecuteUpdate();
count = FGetDataCommand->Parameters->Parameter[1]->Value->GetInt32();
stream = FGetDataCommand->Parameters->Parameter[2]->Value->GetStream(FInstanceOwner);
System::UnicodeString result = FGetDataCommand->Parameters->Parameter[3]->Value->GetWideString();
return result;
}
Which throws an access violation in the ExecuteUpdate() call.
Is there a way I can pass a pointer to the server method and mark it in some way that the stream should be passed back to the calling client?
Remy's response was correct.
The server method is
System::UnicodeString TServerMethods1::GetData(int PatientID, int& count,
TStream*& stream) {
count = 10;
String newSimpleString = "New Simple String";
TByteDynArray theBytes;
theBytes.Length = newSimpleString.Length();
for (int i = 0; i < newSimpleString.Length(); i++) {
theBytes[i] = newSimpleString[i + 1];
}
TDBXBytesStream* newStream =
new TDBXBytesStream(theBytes, newSimpleString.Length());
stream = newStream;
return "StringResult";
}
and the client method is
void __fastcall TForm1::Button2Click(TObject *Sender) {
int count = 1;
TStringStream* stringStream = new TStringStream(String("Passed In"));
TStream* str = stringStream;
String result = ClientModule1->ServerMethods1Client->GetData(1, count, str);
int len = str->Size;
ShowMessage("Result is: '" + result + "' and " + String(count) +
", and stream is " + String(len) + " bytes long");
}
With FInstanceOwner at true (the default as generated), the stream passed in as a client method gets freed the next time the method is called.
On the server, the newly created TDBXBytesStream gets freed the next time the server method is called.
TDBXBytesStream is not a fully functional stream - for example, I could not get CopyFrom to copy from a TStringStream (for example), which is why I use the TByteDynArray.
Also, I could not modify the stream passed in to the server method by writing to it, which is why I needed to assign a new TDBXBytesStream to the passed in reference.
Thanks, Remy, for helping me with this.
my code are as follows
private void photoChooserTask_Completed(object sender, PhotoResult e)
{
if (e.TaskResult == TaskResult.OK)
{
Stream stream = e.ChosenPhoto;
int len = (int)stream.Length;
byte[] PhoteBytes = new byte[len];
stream.Read(PhoteBytes,0,len);
}
}
I am wondering is it possible to convert
byte array(PhoteBytes in this example)
to WriteableBitmap?
Thanks for the help
Try this.
WritableBitmap bitmap = new WritableBitmap(); //Use appropriate constructor
bitmap.SetSource(e.ChosenPhoto);
EDIT: You'll have to use the appropriate constructor. In one WriteableBitmap(Int32, Int32) constructor you can specify the bitmap width and height. See documentation here