Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make png js browserified work with fetch streams in the browser #128

Open
sniggyfigbat opened this issue Mar 31, 2019 · 10 comments
Open

Make png js browserified work with fetch streams in the browser #128

sniggyfigbat opened this issue Mar 31, 2019 · 10 comments

Comments

@sniggyfigbat
Copy link

I'm attempting to use PNGJS to read and write raw pixel data for a browser-based game. I'm using the browserified version of the library (const PNG = require('pngjs/browser').PNG;), and I'm simply attempting to populate a PNG object from a served .png file. As far as I can tell, the library intends me to do this from a stream, and thus I'm using the fetchmethod built into the browser, which returns a ReadableStream.

The appropriate excerpt from my code:

let levelStream = new PNG({ filterType: 4 });

let fetchLevelProm = fetch('./levels/TestLevel.png').then(
	(response) => {
		if (response.status !== 200) {
			console.log('Issue loading level file from server. Status Code: ' + response.status);
			return;
		}
		return response;
	}
)
.then(response => response.body)
.then(body => body.pipeTo(levelStream))
.then(promise => GP.loadLevel(levelStream))
.catch(function(err) {
	console.log('Fetch Error: ', err);
});

This looks like it should work; pipeTo requires a WriteableStream, which the PNG object seems to be(?). Unfortunately, practice does not support theory, as testing this in Chrome throws an error at the pipeTo statement:

TypeError: Failed to execute 'pipeTo' on 'ReadableStream': Illegal invocation

Which isn't very explicit, and leaves me rather flummoxed.

Given my state of confusion, I just wanted to check that I'm not completely misunderstanding something, and I'm not busily using two different non-interactive APIs with exactly the same name or whatever. This is something which should work, right?

@sniggyfigbat
Copy link
Author

Answer: No, you can't, fetch/browser streams are a different API/system to the Node/fs ones.

There's probably a clever solution to this, and it would also nice if someone added a good solution to the library.


Shite Workaround: Don't bother with streams, just parse it:

For posterity; You can actually use the async parse functionality perfectly effectively for this. Here's a bit of sample code where I use it to read a fetch result:

let levelStream;

let fetchLevelProm = fetch('./levels/TestLevel.png').then(
	(response) => {
		if (response.status !== 200) {
			console.log('Issue loading level file from server. Status Code: ' + response.status);
			return;
		}
		
		return response;
	}
)
.then(response => response.arrayBuffer())
.then(buffer => {
	levelStream = new PNG({ filterType:4 }).parse( buffer, function(error, data) {
		if (error != null) { console.log('ERROR: In level image read; ' + error); }
		else { GP.loadLevel(levelStream); }
	});
})
.catch(function(err) {
	console.log('Fetch Error: ', err);
});

Don't be confused by it being called 'levelData', I'm just storing data in a perfectly normal PNG.

@sniggyfigbat
Copy link
Author

PS: Not currently closing, as it still seems a major shortcoming that should probably be addressed. If any contributors see this, feel free to close as a 'won't fix' if you think it's not worth the effort to build in support.

@lukeapage lukeapage changed the title [Question] Does this work with Fetch streams? If so, how? Make png js browserified work with fetch streams in the browser Apr 2, 2019
@lukeapage
Copy link
Collaborator

Sorry no idea, I’ve never needed to use this in the browser. I’ve renamed and will keep it open.

@aksharj
Copy link

aksharj commented Jun 15, 2020

Hi,

Is there any update on this, I am facing a similar issue. I want to pass a fetch api readable stream to node.js writeable stream.

Any help on how to workaround this would be really great, Thanks.

@schacker
Copy link

same error ~

@delebash
Copy link

delebash commented Sep 23, 2021

@sniggyfigbat Your solution works great. How do you write to a file?

Example:

 let writable = await file_handle.createWritable();
let png = new PNG({})

I parse the PNG with the arraybuffer, all works

png.pipeTo(writable)

Then I get error png.pipeTo is not a function

FYI:

If I try await response.body.pipeTo(writable); it works and you can see that response.body is a readable stream in the debug console. However, the png file above shows the data but it is not a readable stream. Do I just convert it to a readable stream. I thought pngjs produced read and write streams but maybe the streams are different in the browser. If I need to convert it to a browser readable stream how do I do that?

Cheers!

@sniggyfigbat
Copy link
Author

@delebash I've no idea, I'm afraid. I haven't touched the project I needed this for in two years, and I don't remember much in the way of details. Sorry!

@delebash
Copy link

TY. I have made some progress. I am now using writable.write(data) instead of pipeTo and it works sort of. Without passing response.arrayBuffer() to PNG I get the correct but unmodified file. When I parse response.arrayBuffer using PNG it writes a 1kb corrupt file. May guess is that I need PNG to return an arrayBuffer but I am not sure how to do that.

@codyzu
Copy link

codyzu commented Mar 20, 2023

I came to the same conclusion as @sniggyfigbat , but using async/await. Obviously if streams worked, that could potentially be more efficient since it could leverage streams to parse large files. Oh well.

Here's my async/await version to decode a fetched image:

async function decode(response) {
  const buffer = await response.arrayBuffer();
  const png = await new Promise((resolve, reject) =>
    new PNG().parse(buffer, (err, data) => 
      err ? reject(err) : resolve(data)
    )
  );
}

// ...

const fetchedImage = await fetch('./a.png');
const decodedImage = await decodeImage(fetchedImage);
console.log('decoded', decodedImage);

@codyzu
Copy link

codyzu commented Mar 20, 2023

No, you can't, fetch/browser streams are a different API/system to the Node/fs ones.

Interestingly node.js has supported web streams since version 16.

The fix in the library probably revolves around replacing the streams it uses with web streams. 🤔

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

No branches or pull requests

6 participants