Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions packages/webgpu/android/cpp/AndroidPlatformContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ class AndroidPlatformContext : public PlatformContext {
return instance.CreateSurface(&surfaceDescriptor);
}

ImageData createImageBitmap(std::string blobId, double offset,
double size) override {
ImageData createImageBitmap(std::string blobId, double offset, double size,
bool premultiplyAlpha) override {
jni::Environment::ensureCurrentThreadIsAttached();

JNIEnv *env = facebook::jni::Environment::current();
Expand All @@ -92,15 +92,16 @@ class AndroidPlatformContext : public PlatformContext {
}

auto data = resolveBlob(env, blobId, offset, size);
return createImageBitmapFromData(data);
return createImageBitmapFromData(data, premultiplyAlpha);
}

void
createImageBitmapAsync(std::string blobId, double offset, double size,
bool premultiplyAlpha,
std::function<void(ImageData)> onSuccess,
std::function<void(std::string)> onError) override {
std::thread([this, blobId = std::move(blobId), offset, size,
onSuccess = std::move(onSuccess),
premultiplyAlpha, onSuccess = std::move(onSuccess),
onError = std::move(onError)]() {
jni::Environment::ensureCurrentThreadIsAttached();
try {
Expand All @@ -109,15 +110,16 @@ class AndroidPlatformContext : public PlatformContext {
throw std::runtime_error("Couldn't get JNI environment");
}
auto data = resolveBlob(env, blobId, offset, size);
auto result = createImageBitmapFromData(data);
auto result = createImageBitmapFromData(data, premultiplyAlpha);
onSuccess(std::move(result));
} catch (const std::exception &e) {
onError(e.what());
}
}).detach();
}

ImageData createImageBitmapFromData(std::span<const uint8_t> data) override {
ImageData createImageBitmapFromData(std::span<const uint8_t> data,
bool /*premultiplyAlpha*/) override {
jni::Environment::ensureCurrentThreadIsAttached();

JNIEnv *env = facebook::jni::Environment::current();
Expand Down Expand Up @@ -179,6 +181,9 @@ class AndroidPlatformContext : public PlatformContext {
result.height = static_cast<int>(bitmapInfo.height);
result.data.resize(bitmapInfo.height * bitmapInfo.stride);
memcpy(result.data.data(), bitmapPixels, result.data.size());
result.premultipliedAlpha =
(bitmapInfo.flags & ANDROID_BITMAP_FLAGS_ALPHA_MASK) ==
ANDROID_BITMAP_FLAGS_ALPHA_PREMUL;

AndroidBitmap_unlockPixels(env, bitmap);

Expand All @@ -189,15 +194,16 @@ class AndroidPlatformContext : public PlatformContext {
}

void createImageBitmapFromDataAsync(
std::span<const uint8_t> data, std::function<void(ImageData)> onSuccess,
std::span<const uint8_t> data, bool premultiplyAlpha,
std::function<void(ImageData)> onSuccess,
std::function<void(std::string)> onError) override {
std::thread([this,
ownedData = std::vector<uint8_t>(data.begin(), data.end()),
onSuccess = std::move(onSuccess),
premultiplyAlpha, onSuccess = std::move(onSuccess),
onError = std::move(onError)]() mutable {
jni::Environment::ensureCurrentThreadIsAttached();
try {
auto result = createImageBitmapFromData(ownedData);
auto result = createImageBitmapFromData(ownedData, premultiplyAlpha);
onSuccess(std::move(result));
} catch (const std::exception &e) {
onError(e.what());
Expand Down
11 changes: 7 additions & 4 deletions packages/webgpu/apple/ApplePlatformContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,21 @@ class ApplePlatformContext : public PlatformContext {
wgpu::Surface makeSurface(wgpu::Instance instance, void *surface, int width,
int height) override;

ImageData createImageBitmap(std::string blobId, double offset,
double size) override;
ImageData createImageBitmap(std::string blobId, double offset, double size,
bool premultiplyAlpha) override;

void
createImageBitmapAsync(std::string blobId, double offset, double size,
bool premultiplyAlpha,
std::function<void(ImageData)> onSuccess,
std::function<void(std::string)> onError) override;

ImageData createImageBitmapFromData(std::span<const uint8_t> data) override;
ImageData createImageBitmapFromData(std::span<const uint8_t> data,
bool premultiplyAlpha) override;

void createImageBitmapFromDataAsync(
std::span<const uint8_t> data, std::function<void(ImageData)> onSuccess,
std::span<const uint8_t> data, bool premultiplyAlpha,
std::function<void(ImageData)> onSuccess,
std::function<void(std::string)> onError) override;

VideoFrameHandle loadVideoFrame(const std::string &path) override;
Expand Down
62 changes: 48 additions & 14 deletions packages/webgpu/apple/ApplePlatformContext.mm
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <TargetConditionals.h>

#import <AVFoundation/AVFoundation.h>
#import <CoreImage/CoreImage.h>
#import <CoreVideo/CoreVideo.h>
#import <React/RCTBlobManager.h>
#import <React/RCTBridge+Private.h>
Expand Down Expand Up @@ -48,7 +49,8 @@ void checkIfUsingSimulatorWithAPIValidation() {
}

ImageData ApplePlatformContext::createImageBitmap(std::string blobId,
double offset, double size) {
double offset, double size,
bool premultiplyAlpha) {
RCTBlobManager *blobManager =
[[RCTBridge currentBridge] moduleForClass:RCTBlobManager.class];
NSData *blobData =
Expand All @@ -60,11 +62,11 @@ void checkIfUsingSimulatorWithAPIValidation() {
throw std::runtime_error("Couldn't retrieve blob data");
}

return createImageBitmapFromData(nsDataToSpan(blobData));
return createImageBitmapFromData(nsDataToSpan(blobData), premultiplyAlpha);
}

void ApplePlatformContext::createImageBitmapAsync(
std::string blobId, double offset, double size,
std::string blobId, double offset, double size, bool premultiplyAlpha,
std::function<void(ImageData)> onSuccess,
std::function<void(std::string)> onError) {
// Resolve blob on current thread (requires RCTBridge access)
Expand All @@ -82,23 +84,48 @@ void checkIfUsingSimulatorWithAPIValidation() {

// blobData is alive during this synchronous call;
// createImageBitmapFromDataAsync copies the span before dispatching
createImageBitmapFromDataAsync(nsDataToSpan(blobData), std::move(onSuccess),
std::move(onError));
createImageBitmapFromDataAsync(nsDataToSpan(blobData), premultiplyAlpha,
std::move(onSuccess), std::move(onError));
}

ImageData
ApplePlatformContext::createImageBitmapFromData(std::span<const uint8_t> data) {
// This avoids a copy by assuming the UIImage/NSImage constructors
// decode `nsData` eagerly before the memory for the wrapped `data`
// is freed.
//
// Since we get the `CGImageRef` from `image` and then throw
// it away, that's a fairly safe assumption.
ApplePlatformContext::createImageBitmapFromData(std::span<const uint8_t> data,
bool premultiplyAlpha) {
NSData *nsData =
[NSData dataWithBytesNoCopy:const_cast<uint8_t *>(data.data())
length:data.size()
freeWhenDone:NO];

if (!premultiplyAlpha) {
CIImage *ciImage =
[CIImage imageWithData:nsData
options:@{kCIImageColorSpace : [NSNull null]}];
if (ciImage != nil) {
// Core Image retains higher precision until it writes straight RGBA8.
size_t width = static_cast<size_t>(CGRectGetWidth(ciImage.extent));
size_t height = static_cast<size_t>(CGRectGetHeight(ciImage.extent));
size_t bytesPerRow = width * 4;
ImageData result;
result.width = static_cast<int>(width);
result.height = static_cast<int>(height);
result.data.resize(height * bytesPerRow);
result.format = wgpu::TextureFormat::RGBA8Unorm;
result.premultipliedAlpha = false;

static CIContext *ciContext = [CIContext contextWithOptions:@{
kCIContextWorkingColorSpace : [NSNull null],
kCIContextOutputPremultiplied : @NO,
}];
[ciContext render:ciImage
toBitmap:result.data.data()
rowBytes:bytesPerRow
bounds:ciImage.extent
format:kCIFormatRGBA8
colorSpace:nil];
return result;
}
}

#if !TARGET_OS_OSX
UIImage *image = [UIImage imageWithData:nsData];
#else
Expand All @@ -125,12 +152,18 @@ void checkIfUsingSimulatorWithAPIValidation() {
result.height = static_cast<int>(height);
result.data.resize(height * bytesPerRow);
result.format = wgpu::TextureFormat::RGBA8Unorm;
result.premultipliedAlpha = true;

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(
result.data.data(), width, height, bitsPerComponent, bytesPerRow,
colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);

if (context == nullptr) {
CGColorSpaceRelease(colorSpace);
throw std::runtime_error("Couldn't create image bitmap context");
}

CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);

CGContextRelease(context);
Expand All @@ -140,7 +173,8 @@ void checkIfUsingSimulatorWithAPIValidation() {
}

void ApplePlatformContext::createImageBitmapFromDataAsync(
std::span<const uint8_t> data, std::function<void(ImageData)> onSuccess,
std::span<const uint8_t> data, bool premultiplyAlpha,
std::function<void(ImageData)> onSuccess,
std::function<void(std::string)> onError) {
// Copy span data into shared_ptr so the dispatch_async block owns the memory
auto ownedData =
Expand All @@ -149,7 +183,7 @@ void checkIfUsingSimulatorWithAPIValidation() {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
@autoreleasepool {
try {
auto result = createImageBitmapFromData(*ownedData);
auto result = createImageBitmapFromData(*ownedData, premultiplyAlpha);
onSuccess(std::move(result));
} catch (const std::exception &e) {
onError(e.what());
Expand Down
9 changes: 6 additions & 3 deletions packages/webgpu/cpp/rnwgpu/PlatformContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ struct ImageData {
size_t width;
size_t height;
wgpu::TextureFormat format;
bool premultipliedAlpha = true;
};

// Pixel layout of a VideoFrame. Determines whether the underlying surface is
Expand Down Expand Up @@ -73,20 +74,22 @@ class PlatformContext {
virtual wgpu::Surface makeSurface(wgpu::Instance instance, void *surface,
int width, int height) = 0;
virtual ImageData createImageBitmap(std::string blobId, double offset,
double size) = 0;
double size, bool premultiplyAlpha) = 0;

// Async version that performs image decoding on a background thread
virtual void
createImageBitmapAsync(std::string blobId, double offset, double size,
bool premultiplyAlpha,
std::function<void(ImageData)> onSuccess,
std::function<void(std::string)> onError) = 0;

// Create ImageBitmap from raw encoded image bytes (PNG/JPEG/etc.)
virtual ImageData
createImageBitmapFromData(std::span<const uint8_t> data) = 0;
virtual ImageData createImageBitmapFromData(std::span<const uint8_t> data,
bool premultiplyAlpha) = 0;

virtual void
createImageBitmapFromDataAsync(std::span<const uint8_t> data,
bool premultiplyAlpha,
std::function<void(ImageData)> onSuccess,
std::function<void(std::string)> onError) = 0;

Expand Down
43 changes: 25 additions & 18 deletions packages/webgpu/cpp/rnwgpu/api/GPUQueue.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "GPUQueue.h"

#include <cstring>
#include <limits>
#include <memory>
#include <vector>
Expand Down Expand Up @@ -128,26 +129,32 @@ void GPUQueue::copyExternalImageToTexture(
throw std::runtime_error("Invalid input for GPUQueue::writeTexture()");
}

if (source->flipY.value_or(false)) {
// Calculate the row size and total size
uint32_t rowSize = bytesPerPixel * source->source->getWidth();
uint32_t totalSize = source->source->getSize();

// Create a new buffer for the flipped data
std::vector<uint8_t> flippedData(totalSize);

// Flip the data vertically
for (uint32_t row = 0; row < source->source->getHeight(); ++row) {
std::memcpy(flippedData.data() +
(source->source->getHeight() - 1 - row) * rowSize,
static_cast<const uint8_t *>(source->source->getData()) +
row * rowSize,
rowSize);
bool flipY = source->flipY.value_or(false);
bool sourcePremultipliedAlpha = source->source->isPremultiplied();
bool destinationPremultipliedAlpha =
destination->premultipliedAlpha.value_or(false);
bool convertAlpha = sourcePremultipliedAlpha != destinationPremultipliedAlpha;

if (flipY || convertAlpha) {
size_t rowSize = bytesPerPixel * source->source->getWidth();
size_t totalSize = source->source->getSize();
auto sourceData = static_cast<const uint8_t *>(source->source->getData());
std::vector<uint8_t> uploadData(totalSize);

if (flipY) {
for (size_t row = 0; row < source->source->getHeight(); ++row) {
std::memcpy(uploadData.data() +
(source->source->getHeight() - 1 - row) * rowSize,
sourceData + row * rowSize, rowSize);
}
} else {
std::memcpy(uploadData.data(), sourceData, totalSize);
}
// Use the flipped data for writing to texture
_instance.WriteTexture(&dst, flippedData.data(), totalSize, &layout, &sz);
} else {

ImageBitmap::convertAlpha(uploadData, sourcePremultipliedAlpha,
destinationPremultipliedAlpha);
_instance.WriteTexture(&dst, uploadData.data(), totalSize, &layout, &sz);
} else {
_instance.WriteTexture(&dst, source->source->getData(),
source->source->getSize(), &layout, &sz);
}
Expand Down
34 changes: 32 additions & 2 deletions packages/webgpu/cpp/rnwgpu/api/ImageBitmap.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@ class ImageBitmap : public NativeObject<ImageBitmap> {
public:
static constexpr const char *CLASS_NAME = "ImageBitmap";

explicit ImageBitmap(ImageData &imageData)
: NativeObject(CLASS_NAME), _imageData(imageData) {}
ImageBitmap(ImageData &imageData, bool premultipliedAlpha)
: NativeObject(CLASS_NAME), _imageData(imageData) {
convertAlpha(_imageData.data, _imageData.premultipliedAlpha,
premultipliedAlpha);
_imageData.premultipliedAlpha = premultipliedAlpha;
}

size_t getWidth() { return _imageData.width; }

Expand All @@ -26,6 +30,32 @@ class ImageBitmap : public NativeObject<ImageBitmap> {

size_t getSize() { return _imageData.data.size(); }

bool isPremultiplied() { return _imageData.premultipliedAlpha; }

static void convertAlpha(std::vector<uint8_t> &data,
bool sourcePremultipliedAlpha,
bool destinationPremultipliedAlpha) {
if (sourcePremultipliedAlpha == destinationPremultipliedAlpha) {
return;
}

for (size_t i = 0; i + 3 < data.size(); i += 4) {
uint32_t alpha = data[i + 3];
for (size_t channel = 0; channel < 3; ++channel) {
uint32_t value = data[i + channel];
if (destinationPremultipliedAlpha) {
value = (value * alpha + 127) / 255;
} else if (alpha == 0) {
value = 0;
} else {
value = (value * 255 + alpha / 2) / alpha;
value = value > 255 ? 255 : value;
}
data[i + channel] = static_cast<uint8_t>(value);
}
}
}

void close() {
_imageData.data.clear();
_imageData.data.shrink_to_fit();
Expand Down
Loading
Loading