This repository has been archived by the owner on Apr 8, 2022. It is now read-only.
forked from marcwieserdev/UE4-LeapMotionPlugin
/
LeapImage.cpp
441 lines (362 loc) · 13.7 KB
/
LeapImage.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
#include "LeapMotionPrivatePCH.h"
#include "RHI.h"
class PrivateLeapImage
{
public:
Leap::Image LeapImage;
FUpdateTextureRegion2D UpdateTextureRegion;
FDateTime ImageTimeUtc;
UTexture2D* validImagePointer(UTexture2D* Pointer, int32 PWidth, int32 PHeight, EPixelFormat Format, bool GammaCorrectionUsed = true);
UTexture2D* Texture8FromLeapImage(int32 SrcWidth, int32 SrcHeight, uint8* ImageBuffer);
UTexture2D* Texture32FromLeapImage(int32 SrcWidth, int32 SrcHeight, uint8* ImageBuffer);
void UpdateTextureRegions(UTexture2D* Texture, int32 MipIndex, uint32 NumRegions, FUpdateTextureRegion2D* Regions, uint32 SrcPitch, uint32 SrcBpp, uint8* SrcData, bool bFreeData);
UTexture2D* EnqueuedTexture32FromLeapImage(int32 SrcWidth, int32 SrcHeight, uint8* ImageBuffer);
UTexture2D* Texture32FromLeapDistortion(int32 SrcWidth, int32 SrcHeight, float* ImageBuffer); //optimized, requires shader channel flipping TODO: change from shader to CPU cost as distortions are fetched once!
UTexture2D* Texture32PrettyFromLeapDistortion(int32 SrcWidth, int32 SrcHeight, float* ImageBuffer); //unoptimized roughly +1ms
UTexture2D* Texture128FromLeapDistortion(int32 SrcWidth, int32 SrcHeight, float* ImageBuffer); //4x float 32
UTexture2D* TextureFFromLeapDistortion(int32 SrcWidth, int32 SrcHeight, float* ImageBuffer);
UTexture2D* Texture32FromLeapImageWithGrid(int32 SrcWidth, int32 SrcHeight, uint8* ImageBuffer);
int32 InvalidSizesReported = 0;
bool IgnoreTwoInvalidSizesDone = false;
ULeapImage* Self;
};
ULeapImage::ULeapImage(const FObjectInitializer &ObjectInitializer) : UObject(ObjectInitializer)//, Private(new PrivateLeapImage())
{
Private = new PrivateLeapImage();
Private->Self = this;
//default not using gamma correction
UseGammaCorrection = false;
}
ULeapImage::~ULeapImage()
{
delete Private;
}
UTexture2D* PrivateLeapImage::validImagePointer(UTexture2D* Pointer, int32 PWidth, int32 PHeight, EPixelFormat Format, bool GammaCorrectionUsed)
{
//Make sure we're valid
if (!Self->IsValid)
{
UE_LOG(LeapPluginLog, Error, TEXT("Warning! Invalid Image."));
return nullptr;
}
//Instantiate the texture if needed
if (Pointer == nullptr)
{
if (PWidth == 0 || PHeight == 0)
{
//Spit out only valid errors, two size 0 textures will be attempted per pointer,
//unable to filter the messages out without this (or a pointer to Leap Controller, but this uses less resources).
if (IgnoreTwoInvalidSizesDone)
{
UE_LOG(LeapPluginLog, Error, TEXT("Warning! Leap Image SDK access is denied, please enable image support from the Leap Controller before events emit (e.g. at BeginPlay)."));
}
else
{
InvalidSizesReported++;
if (InvalidSizesReported == 2)
{
IgnoreTwoInvalidSizesDone = true;
}
}
return nullptr;
}
UE_LOG(LeapPluginLog, Log, TEXT("Created Leap Image Texture Sized: %d, %d, format %d"), PWidth, PHeight, (int)Format);
Pointer = UTexture2D::CreateTransient(PWidth, PHeight, Format); //PF_B8G8R8A8
Pointer->UpdateResource();
UpdateTextureRegion = FUpdateTextureRegion2D(0, 0, 0, 0, PWidth, PHeight);
//change texture settings
if (!GammaCorrectionUsed)
{
Pointer->SRGB = 0;
}
//keep it around?
}
//If the size changed, recreate the image (NB: GC may release the platform data in which case we need to recreate it (since 4.7)
if (!UtilityPointerIsValid(Pointer->PlatformData) ||
Pointer->PlatformData->SizeX != PWidth ||
Pointer->PlatformData->SizeY != PHeight)
{
UE_LOG(LeapPluginLog, Log, TEXT("ReCreated Leap Image Texture Sized: %d, %d. Old Size: %d, %d"), PWidth, PHeight, Pointer->PlatformData->SizeX, Pointer->PlatformData->SizeY);
Pointer = UTexture2D::CreateTransient(PWidth, PHeight, Format);
Pointer->UpdateResource();
UpdateTextureRegion = FUpdateTextureRegion2D(0, 0, 0, 0, PWidth, PHeight);
}
return Pointer;
}
//Grayscale average texture
UTexture2D* PrivateLeapImage::Texture32FromLeapImage(int32 SrcWidth, int32 SrcHeight, uint8* ImageBuffer)
{
// Lock the texture so it can be modified
if (Self->PImagePointer == nullptr) {
UE_LOG(LeapPluginLog, Log, TEXT("Image is null!"));
return nullptr;
}
uint8* MipData = static_cast<uint8*>(Self->PImagePointer->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE));
// Create base mip.
uint8* DestPtr = nullptr;
const uint8* SrcPtr = nullptr;
for (int32 y = 0; y<SrcHeight; y++)
{
DestPtr = &MipData[(SrcHeight - 1 - y) * SrcWidth * sizeof(FColor)];
SrcPtr = const_cast<uint8*>(&ImageBuffer[(SrcHeight - 1 - y) * SrcWidth]);
for (int32 x = 0; x<SrcWidth; x++)
{
//Grayscale, copy to all channels
*DestPtr++ = *SrcPtr;
*DestPtr++ = *SrcPtr;
*DestPtr++ = *SrcPtr;
*DestPtr++ = 0xFF;
SrcPtr++;
}
}
// Unlock the texture
Self->PImagePointer->PlatformData->Mips[0].BulkData.Unlock();
Self->PImagePointer->UpdateResource();
return Self->PImagePointer;
}
void PrivateLeapImage::UpdateTextureRegions(UTexture2D* Texture, int32 MipIndex, uint32 NumRegions, FUpdateTextureRegion2D* Regions, uint32 SrcPitch, uint32 SrcBpp, uint8* SrcData, bool bFreeData)
{
if (Texture->Resource)
{
struct FUpdateTextureRegionsData
{
FTexture2DResource* Texture2DResource;
int32 MipIndex;
uint32 NumRegions;
FUpdateTextureRegion2D* Regions;
uint32 SrcPitch;
uint32 SrcBpp;
uint8* SrcData;
};
FUpdateTextureRegionsData* RegionData = new FUpdateTextureRegionsData;
RegionData->Texture2DResource = (FTexture2DResource*)Texture->Resource;
RegionData->MipIndex = MipIndex;
RegionData->NumRegions = NumRegions;
RegionData->Regions = Regions;
RegionData->SrcPitch = SrcPitch;
RegionData->SrcBpp = SrcBpp;
RegionData->SrcData = SrcData;
ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
UpdateTextureRegionsData,
FUpdateTextureRegionsData*, RegionData, RegionData,
bool, bFreeData, bFreeData,
{
for (uint32 RegionIndex = 0; RegionIndex < RegionData->NumRegions; ++RegionIndex)
{
int32 CurrentFirstMip = RegionData->Texture2DResource->GetCurrentFirstMip();
if (RegionData->MipIndex >= CurrentFirstMip)
{
RHIUpdateTexture2D(
RegionData->Texture2DResource->GetTexture2DRHI(),
RegionData->MipIndex - CurrentFirstMip,
RegionData->Regions[RegionIndex],
RegionData->SrcPitch,
RegionData->SrcData
+ RegionData->Regions[RegionIndex].SrcY * RegionData->SrcPitch
+ RegionData->Regions[RegionIndex].SrcX * RegionData->SrcBpp
);
}
}
if (bFreeData)
{
FMemory::Free(RegionData->Regions);
FMemory::Free(RegionData->SrcData);
}
delete RegionData;
});
}
}
//Efficiency upgrade - Use render thread to update image - Not currently working properly TODO: fix this version of image fetching
UTexture2D* PrivateLeapImage::EnqueuedTexture32FromLeapImage(int32 SrcWidth, int32 SrcHeight, uint8* ImageBuffer)
{
// Lock the texture so it can be modified
if (Self->PImagePointer == nullptr)
{
return nullptr;
}
UpdateTextureRegions(Self->PImagePointer, 0, 1, &UpdateTextureRegion, 4 * SrcWidth, 4, ImageBuffer, false);
return Self->PImagePointer;
}
//Used to help alignment tests
UTexture2D* PrivateLeapImage::Texture32FromLeapImageWithGrid(int32 SrcWidth, int32 SrcHeight, uint8* ImageBuffer)
{
// Lock the texture so it can be modified
if (Self->PImagePointer == nullptr)
return nullptr;
uint8* MipData = static_cast<uint8*>(Self->PImagePointer->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE));
// Create base mip.
uint8* DestPtr = nullptr;
const uint8* SrcPtr = nullptr;
//mix in the grid
//Leap images are 320x240 with 4,8,10,20,40 as common factors
int gridSize = 40;
for (int32 y = 0; y < SrcHeight; y++)
{
DestPtr = &MipData[(SrcHeight - 1 - y) * SrcWidth * sizeof(FColor)];
SrcPtr = const_cast<uint8*>(&ImageBuffer[(SrcHeight - 1 - y) * SrcWidth]);
for (int32 x = 0; x < SrcWidth; x++)
{
//Grid pixel 0x77 to make the grid, vary color by row
if( (y % gridSize) == 0 || (x % gridSize) == 0)
{
//if(y % gridSize) == 0 || (x % gridSize) == 0)
//float fractionColor1 = ((float)y / (float)SrcHeight) * (float)0x77;
//float fractionColor2 = ((float)x / (float)SrcWidth) * (float)0x77;
*DestPtr++ = (uint8) 0x77;
*DestPtr++ = (uint8) 0x77;
*DestPtr++ = (uint8) 0x77;
*DestPtr++ = 0x77;
}
else
{
//Grayscale, copy to all channels
*DestPtr++ = *SrcPtr;
*DestPtr++ = *SrcPtr;
*DestPtr++ = *SrcPtr;
*DestPtr++ = 0xFF;
/**DestPtr++ = 0x00;
*DestPtr++ = 0x00;
*DestPtr++ = 0x00;
*DestPtr++ = 0xFF;*/
}
SrcPtr++;
}
}
// Unlock the texture
Self->PImagePointer->PlatformData->Mips[0].BulkData.Unlock();
Self->PImagePointer->UpdateResource();
return Self->PImagePointer;
}
//This gives a good looking distortion but it does not cull invalid values nor does it flip the V channel to UE format
//32bit, 8 per channel. Downsampled.
UTexture2D* PrivateLeapImage::Texture32PrettyFromLeapDistortion(int32 SrcWidth, int32 SrcHeight, float* ImageBuffer)
{
// Lock the texture so it can be modified
if (Self->PDistortionPointer == nullptr)
{
return nullptr;
}
int32 DestWidth = SrcWidth / 2; // Put 2 floats in the R and G channels
int32 DestHeight = SrcHeight;
// Lock the texture so it can be modified
uint8* MipData = static_cast<uint8*>(Self->PDistortionPointer->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE));
// Create base mip.
uint8* DestPtr = nullptr;
const float* SrcPtr = ImageBuffer;
DestPtr = MipData;
for (int d = 0; d < SrcWidth * SrcHeight; d += 2)
{
float dX = ImageBuffer[d];
float dY = ImageBuffer[d + 1];
int destIndex = d * 2;
if (!((dX < 0) || (dX > 1)) && !((dY < 0) || (dY > 1))) {
//R
DestPtr[destIndex] = (uint8)FMath::Clamp<int32>(FMath::TruncToInt(dX *255.f), 0, 255);
//G
DestPtr[destIndex + 1] = (uint8)FMath::Clamp<int32>(255 - FMath::TruncToInt(dY *255.f), 0, 255);
//B
DestPtr[destIndex + 2] = 0;
//A
DestPtr[destIndex + 3] = 255;
//if (d == (SrcHeight / 2) * SrcWidth + (SrcWidth / 2))
// UE_LOG(LeapPluginLog, Log, TEXT("(%d, %d, %d, %d), (%1.3f,%1.3f) @pos: %d"), DestPtr[destIndex], DestPtr[destIndex + 1], DestPtr[destIndex + 2], DestPtr[destIndex + 3],dX, dY, d);
}
else
{
//R
DestPtr[destIndex] = 0;
//G
DestPtr[destIndex + 1] = 0;
//B
DestPtr[destIndex + 2] = 255;
//A
DestPtr[destIndex + 3] = 255;
}
}
// Unlock the texture
Self->PDistortionPointer->PlatformData->Mips[0].BulkData.Unlock();
Self->PDistortionPointer->UpdateResource();
return Self->PDistortionPointer;
}
//Optimized 128bit format, one float per channel, two channels unused but the function works as expected.
UTexture2D* PrivateLeapImage::Texture128FromLeapDistortion(int32 SrcWidth, int32 SrcHeight, float* ImageBuffer)
{
// Lock the texture so it can be modified
if (Self->PDistortionPointer == nullptr)
{
return nullptr;
}
int32 DestWidth = SrcWidth / 2; // Put 2 floats in the R and G channels
int32 DestHeight = SrcHeight;
// Lock the texture so it can be modified
float* MipData = static_cast<float*>(Self->PDistortionPointer->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE));
// Create base mip.
float* DestPtr = nullptr;
const float* SrcPtr = ImageBuffer;
DestPtr = MipData;
int32 length = SrcWidth * SrcHeight;
for (int d = 0; d < length; d += 2)
{
float dX = ImageBuffer[d];
float dY = ImageBuffer[d + 1];
int destIndex = d * 2;
//R
DestPtr[destIndex] = dX;
//G
DestPtr[destIndex + 1] = dY;
//B
DestPtr[destIndex + 2] = 0.f;
//A
DestPtr[destIndex + 3] = 0.f;
}
// Unlock the texture
Self->PDistortionPointer->PlatformData->Mips[0].BulkData.Unlock();
Self->PDistortionPointer->UpdateResource();
return Self->PDistortionPointer;
}
UTexture2D* ULeapImage::Texture()
{
PImagePointer = Private->validImagePointer(PImagePointer, Width, Height, PF_B8G8R8A8, UseGammaCorrection);
//Normal
return Private->Texture32FromLeapImage(Width, Height, (uint8*)Private->LeapImage.data());
//Enqueued - render thread, Todo: enable a working version of this optimization
//return Private->EnqueuedTexture32FromLeapImage(Width, Height, (uint8*)Private->leapImage.data());
}
UTexture2D* ULeapImage::Distortion()
{
PDistortionPointer = Private->validImagePointer(PDistortionPointer, DistortionWidth / 2, DistortionHeight, PF_A32B32G32R32F); //32bit per channel
//return Private->Texture32FromLeapDistortion(DistortionWidth, DistortionHeight, (float*)Private->leapImage.distortion());
return Private->Texture128FromLeapDistortion(DistortionWidth, DistortionHeight, (float*)Private->LeapImage.distortion());
}
UTexture2D* ULeapImage::DistortionUE()
{
PDistortionPointer = Private->validImagePointer(PDistortionPointer, DistortionWidth / 2, DistortionHeight, PF_R8G8B8A8);
return Private->Texture32PrettyFromLeapDistortion(DistortionWidth, DistortionHeight, (float*)Private->LeapImage.distortion());
}
FVector ULeapImage::Rectify(FVector uv) const
{
Leap::Vector vect = Leap::Vector(uv.X, uv.Y, uv.Z);
vect = Private->LeapImage.rectify(vect);
return FVector(vect.x, vect.y, vect.z);
}
FVector ULeapImage::Warp(FVector xy) const
{
Leap::Vector vect = Leap::Vector(xy.X, xy.Y, xy.Z);
vect = Private->LeapImage.warp(vect);
return FVector(vect.x, vect.y, vect.z);
}
void ULeapImage::SetLeapImage(const Leap::Image &LeapImage)
{
Private->LeapImage = LeapImage;
DistortionHeight = Private->LeapImage.distortionHeight();
DistortionWidth = Private->LeapImage.distortionWidth();
Height = Private->LeapImage.height();
Id = Private->LeapImage.id();
IsValid = Private->LeapImage.isValid();
RayOffsetX = Private->LeapImage.rayOffsetX();
RayOffsetY = Private->LeapImage.rayOffsetY();
RayScaleX = Private->LeapImage.rayScaleX();
RayScaleY = Private->LeapImage.rayScaleY();
//Timestamp = Private->LeapImage.timestamp(); //TODO: fix did leap image remove timestamp in 3.0?
Width = Private->LeapImage.width();
}