Skip to content

Conversation

@alexchuber
Copy link
Contributor

This PR adds the glue code to enable DumpData usage in BN.

Changes:

  • Factor out core image encoding to a separate function matching NativeEncoding's own exposed function name. This follows the current nativeOverride pattern for module-level functions (see the MathHelper class.)
  • Remove the bitmaprenderer path. I initially planned for Native to polyfill this, but that turned out to be not a good idea.

…ot assignable to type '(pixelData: Uint8Array<ArrayBufferLike>, width: number, height: number, mimeType: string, invertY: boolean, quality?: number | undefined) => Promise<...>'.

              Type 'unknown' is not assignable to type 'Promise<ArrayBuffer>'.
@bjsplat
Copy link
Collaborator

bjsplat commented Oct 29, 2025

Please make sure to label your PR with "bug", "new feature" or "breaking change" label(s).
To prevent this PR from going to the changelog marked it with the "skip changelog" label.

reject(new Error("DumpData: Failed to convert canvas to blob."));
} else if (blob.type !== mimeType) {
// The MIME type of a canvas.toBlob() output can be different from the one requested if the browser does not support the requested format
reject(new Error(`DumpData: Failed to convert canvas to blob as ${mimeType}. Got ${blob.type} instead.`));
Copy link
Contributor Author

@alexchuber alexchuber Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking change, but I think it makes sense to do, considering the API contract is to just return the binary data-- with the expectation that it matches the requested MIME type.

This change would also enable the changes proposed in #17366.

But there's one case that I'm not sure how to handle: when DumpData is used just to download an image, and the user doesn't care about the return type. I think ScreenshotTools is an example of this.
How should this be handled?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking change, but I think it makes sense to do, considering the API contract is to just return the binary data-- with the expectation that it matches the requested MIME type.

I would say this is a bug fix so it's okay.

But there's one case that I'm not sure how to handle: when DumpData is used just to download an image, and the user doesn't care about the return type. I think ScreenshotTools is an example of this.
How should this be handled?

Maybe we can have an optional mimeType and we select the best one in the implementation?

Copy link
Contributor Author

@alexchuber alexchuber Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say this is a bug fix so it's okay.

OK, I'll leave this be. Thanks.

Maybe we can have an optional mimeType and we select the best one in the implementation?

By implementation, do you mean the implementation of ScreenshotTools? If so, it already does something like this, defaulting to PNG if no mime type is supplied.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking in this function since that was my interpretation of your question. Maybe we can sync offline quickly.

@bjsplat
Copy link
Collaborator

bjsplat commented Oct 29, 2025

@bjsplat
Copy link
Collaborator

bjsplat commented Oct 29, 2025

@bjsplat
Copy link
Collaborator

bjsplat commented Oct 29, 2025

@bjsplat
Copy link
Collaborator

bjsplat commented Oct 29, 2025

@alexchuber alexchuber requested a review from bghgary October 29, 2025 20:53
@bghgary bghgary requested a review from ryantrem October 29, 2025 20:55
* @internal
*/
@nativeOverride
public static async EncodeImageAsync(pixelData: Uint8Array, width: number, height: number, mimeType: string, invertY: boolean, quality?: number): Promise<ArrayBuffer> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As of now, it seems the two approaches to using nativeOverride for module-level function are:

  1. what's here now: a helper class with static-only functions
  2. export const EncodeImageAsync = nativeOverride(async (....) => { ... })

Is this correct? @ryantrem

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the second bullet is a thing as far as I recall and from a quick glance at the code. I think we could make something like that work though, and avoid introducing a new class.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha. Mainly wanted to check if any existing approaches could work. I’ll stick with the class approach for now to keep the PR focused :)

reject(new Error("DumpData: Failed to convert canvas to blob."));
} else if (blob.type !== mimeType) {
// The MIME type of a canvas.toBlob() output can be different from the one requested if the browser does not support the requested format
reject(new Error(`DumpData: Failed to convert canvas to blob as ${mimeType}. Got ${blob.type} instead.`));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking change, but I think it makes sense to do, considering the API contract is to just return the binary data-- with the expectation that it matches the requested MIME type.

I would say this is a bug fix so it's okay.

But there's one case that I'm not sure how to handle: when DumpData is used just to download an image, and the user doesn't care about the return type. I think ScreenshotTools is an example of this.
How should this be handled?

Maybe we can have an optional mimeType and we select the best one in the implementation?

}

const resources = await _GetDumpResourcesAsync();
const buffer = await EncodingHelper.EncodeImageAsync(data as Uint8Array, width, height, mimeType, invertY, quality);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it valid to convert ArrayBufferView to Uint8Array? I'm not sure this will work in all cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely not valid :) I meant to think about this more.

On the one hand, DumpData's usage of engine.createRawTexture implies that the data is expected to be UNSIGNED_BYTE.
On the other hand, I have no clue why this hasn't been a problem yet. My guess is that DumpData is usually used in conjunction with readPixels, which returns either float array or uint8 array

target: any,
propertyKey: string,
descriptor: TypedPropertyDescriptor<(...params: Parameters<T>) => unknown>,
descriptor: TypedPropertyDescriptor<(...params: Parameters<T>) => any>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious why this change was needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To address this guy:
image
without touching this function too much.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a more elegant way?

* @internal
*/
@nativeOverride
public static async EncodeImageAsync(pixelData: Uint8Array, width: number, height: number, mimeType: string, invertY: boolean, quality?: number): Promise<ArrayBuffer> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the second bullet is a thing as far as I recall and from a quick glance at the code. I think we could make something like that work though, and avoid introducing a new class.


texture.dispose();

Tools.ToBlob(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a ToBlobAsync that is promise returning?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could. It wouldn't be as simple as replacing with ToBlob with ToBlobAsync, though, as the canvas draw + toBlob must be atomic. Guess we could use an AsyncLock?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants