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

File.writeFile() refuses to create binary file correctly (png image) ionic 2 #806

Closed
mhartington opened this issue Nov 18, 2016 · 9 comments · Fixed by #1553
Closed

File.writeFile() refuses to create binary file correctly (png image) ionic 2 #806

mhartington opened this issue Nov 18, 2016 · 9 comments · Fixed by #1553

Comments

@mhartington
Copy link
Contributor

From @shrike71 on November 18, 2016 10:17

Short description of the problem:

File.writeFile() creates a PNG file of 0 bytes when trying to write a Blob made from base64 data

screenshot_20161116-151450

What behavior are you expecting?

Base64 data read from a TEXT column in the database is formatted and converted into a Blob, and saved to the application data directory as a PNG image.

Steps to reproduce:

  1. Install recent Ionic 2 / Cordova / NodeJS as illustrated below:
Your system information:

Cordova CLI: 6.3.1
Gulp version:  CLI version 3.9.1
Gulp local:
Ionic Framework Version: 2.0.0-rc.2
Ionic CLI Version: 2.1.1
Ionic App Lib Version: 2.1.1
Ionic App Scripts Version: 0.0.39
OS: Windows 10
Node Version: v6.7.0
  1. Ensure that cordova-plugin-file version 4.3.0 is installed
  2. Examine / Reproduce the code below to observe a 0 bytes file written when trying to write a Blob as an image/png.

Detail

I am trying to create a file that consists of base64 data stored in the db. The rendered equivalent of the data is a small anti-aliased graph curve in black on a transparent background (never more that 300 x 320 pixels) that has previously been created and stored from a canvas element. I have independently verified that the stored base64 data is indeed correct by rendering it at one of various base64 encoders/decoders available online.

The development platform is Windows 10, and i've been testing directly on a Samsung Galaxy S7 and S4 so far.

I know that the base64 data has to be converted into binary data (as a Blob) first, as File does not yet support writing base64 directly in to an image file. I found various techniques with which to do this, and the code which seems to suit my needs the most (and reflects a similar way I would have done it in java is illustrated below):

Main code from constructor:
this.platform.ready().then(() => {
      this.graphDataService.getDataItem(this.job.id).then((data) =>{
        console.log("getpic:");

        let imgWithMeta = data.split(",") 
        // base64 data
        let imgData = imgWithMeta[1].trim();
        // content type
        let imgType = imgWithMeta[0].trim().split(";")[0].split(":")[1];
        
        console.log("imgData:",imgData);
        console.log("imgMeta:",imgType);
        console.log("aftergetpic:");

        // this.fs is correctly set to cordova.file.externalDataDirectory
        let folderpath = this.fs;
        let filename = "dotd_test.png";

        File.resolveLocalFilesystemUrl(this.fs).then( (dirEntry) => {
            console.log("resolved dir with:", dirEntry);
            this.savebase64AsImageFile(dirEntry.nativeURL,filename,imgData,imgType);
        });
      });

    });
Saves the image with File.writefile():
// save the image with File.writeFile()
savebase64AsImageFile(folderpath,filename,content,contentType){

      // Convert the base64 string in a Blob
      let data:Blob = this.b64toBlob(content,contentType,512);
      
      console.log("file location attempt is:",folderpath + filename);

      File.writeFile(
        folderpath,
        filename,
        data,
        {replace: true}
      ).then(
        _ => {console.log("write complete:")}
      ).catch(
        err => {
          console.log("file create failed:",err);
        }
      );    
  }

I have tried dozens of different decoding techniques, but the effect is the same. However, if i hard code simple text data into the writeFile() section, like so:

File.writeFile(
        folderpath,
        "test.txt",
        "the quick brown fox jumps over the lazy dog",
        {replace: true}
      )

A text file IS created correctly in the expected application location with the text string above in it. However, I've also noticed that whether the file is the 0 bytes PNG, or the working text file above, in both cases the ".then()" consequence clause of the File Promise never fires.

Helper to convert base64 to Blob:
// convert base64 to Blob
b64toBlob(b64Data, contentType, sliceSize) {

          //console.log("data packet:",b64Data);
          //console.log("content type:",contentType);
          //console.log("slice size:",sliceSize);

          let byteCharacters = atob(b64Data);
          
          let byteArrays = [];

          for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
              let slice = byteCharacters.slice(offset, offset + sliceSize);

              let byteNumbers = new Array(slice.length);
              for (let i = 0; i < slice.length; i++) {
                  byteNumbers[i] = slice.charCodeAt(i);
              }

              let byteArray = new Uint8Array(byteNumbers);

              byteArrays.push(byteArray);

          }
          

        console.log("size of bytearray before blobbing:", byteArrays.length);
        console.log("blob content type:", contentType);

        let blob = new Blob(byteArrays, {type: contentType});

        // alternative way WITHOUT chunking the base64 data
        // let blob = new Blob([atob(b64Data)],  {type: contentType});

        return blob;
  }

Additionally, I swapped the above method and used the Ionic 2 native Base64-To-Gallery library to create the images, which worked without a problem. However, having the images in the user's picture gallery or camera roll is not an option for me as I do not wish to risk a user's own pictures while marshalling / packing / transmitting / deleting the data-rendered images. The images should be created and managed as part of the app.

Other information: (e.g. stacktraces, related issues, suggestions how to fix, stackoverflow links, forum links, etc)

User marcus-robinson seems to have experienced a similar issue outlined here, but it was across all file types, and not just binary types as seems to be the case here. Also, the issue seems to have been closed:

ionic-team/ionic-framework#5638

Which Ionic Version? Ionic 2.x

Copied from original issue: ionic-team/ionic-framework#9228

@wwoods
Copy link

wwoods commented Nov 23, 2016

Is this not due to #789? options.replace is currently inverted.

@phattranky
Copy link

phattranky commented Jan 1, 2017

I got the same problem when try to use low-level API

Your system information:

Cordova CLI: 6.4.0
Gulp version:  CLI version 3.9.1
Gulp local:
Ionic Framework Version: 2.0.0-rc.4
Ionic CLI Version: 2.1.12
Ionic App Lib Version: 2.1.12
Ionic App Scripts Version: 0.0.47
OS: Windows 10
Node Version: v7.2.1
import { Injectable } from '@angular/core';
import { File } from 'ionic-native';

declare const cordova: any;
declare const resolveLocalFileSystemURL: any;
declare const requestFileSystem: any;
declare const LocalFileSystem: any;

@Injectable()
export class StorageService {
  fs: string = cordova.file.dataDirectory;

  writeFile(fileName: string, fileBlob: any) {
    resolveLocalFileSystemURL(this.fs, (dir) => {
      console.log('Access to the directory granted succesfully');
      dir.getFile(fileName, {create: true}, (file) => {
          console.log('File created succesfully.');
          file.createWriter((fileWriter) => {
              console.log('Writing content to file', fileWriter);
              fileWriter.onwriteend = (e) => {
               console.log('Write completed.', e);
              };
              fileWriter.write(fileBlob);
          }, () => {
            alert('Unable to save file in path ' + this.fs);
          });
      });
    });

    // console.log('begin write file', fileBlob);
    // File.writeFile(
    //   this.fs,
    //   fileName,
    //   fileBlob,
    //   {replace: true}
    // ).then(
    //   _ => {console.log("write complete:")}
    // ).catch(
    //   err => {
    //     console.log("file create failed:",err);
    //   }
    // );    
  }
};

@ihadeed
Copy link
Collaborator

ihadeed commented Jan 1, 2017

@phattranky which Ionic Native version?

@phattranky
Copy link

phattranky commented Jan 1, 2017

@ihadeed the latest version
"ionic-native": "^2.2.13"

But when i try to write a text file, it's working well. Like this

fileWriter.write(['sdasdasdasd']);

I only got the problem when try to write the blob file. I use the method like @mhartington to convert from Canvas Uri to Blob.

Hope my info useful

@phattranky
Copy link

phattranky commented Jan 1, 2017

@ihadeed, @mhartington

When i write the file with ArrayBuffer instead Blob type. It's working well (Both on Lower Level API and Ionic Native File (File.write)). So my temporary solution here is use the below function to write the file as ArrayBuffer type

const base64ToArrayBuffer = (base64) => {
  const binary_string =  window.atob(base64);
  const len = binary_string.length;
  const bytes = new Uint8Array(len);
  for (let i = 0; i < len; i++) {
    bytes[i] = binary_string.charCodeAt(i);
  }
  return bytes.buffer;
}

When use the Blob type. i debug into the cordova-plugin-file, i see the FileReader onload not callback. So i think the problem from there. But don't know why. Then i test the FileReader outside the plugin. I just read my blob file with FileReader, but it not callback to the onload like cordova-plugin-file. Don't know what happen with the FileReader in Angular 2 or Ionic 2.

I'm sure the Blob file is correct. Because i converted it to UrlObject and assign to html Img tag, the image show correctly

URL.createObjectURL(blob);

@brunomrpx
Copy link

It works fine for me in iOS (10.2.1) but in Android i have the same problem. Using this solution with ArrayBuffer solved the problem in Android.

@ihadeed ihadeed removed this from the v2.2.6 milestone Feb 10, 2017
@michael-xd
Copy link
Contributor

I've got the same problem with a pdf file blob. I didn't understand the ArrayBuffer solution, once fileWriter.write or File.writeFile only accepts a string or blob typed parameter. Could someone detail the solution a little more?

@kctang
Copy link

kctang commented Mar 9, 2017

I had similar issues writing Blob - the promise will not resolve. Found out that it was due to Angular's zone issue as described here.

The workaround to wrap FileReader did the job for me.

@anchitdurairaj
Copy link

I have got the same problem while writing the image to device(Android) , understood from the above information that writing as blog make the problem , but the fileWriter method will only accept blob and will never accept Array buffer , can any one explain in brief how to write the image captured from camera to a location in the device ...

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

Successfully merging a pull request may close this issue.

9 participants