0

Calling

File picturesDir = Services.get(StorageService.class)
            .flatMap(s -> s.getPublicStorage("Pictures"))
            .orElseThrow(() -> new RuntimeException("Error retrieving public storage")); 
for (File pic : picturesDir.listFiles()) {
        System.out.println("file " + pic.getName());
}

lists no files. I think it should list all files from my Image-Gallery on iPhone.

Calling s.getPublicStorage("") it lists two folders though: gluon, Pictures.

How can i access it properly?

tonimaroni
  • 1,062
  • 10
  • 19
  • If you have a look at the iOS [implementation](https://bitbucket.org/gluon-oss/charm-down/src/1942aa27c6b60d890511667cf64c3f9d8a0e69b6/plugins/plugin-storage/ios/src/main/native/Storage.m?at=default&fileviewer=file-view-default#Storage.m-52) of the Storage service you'll notice that the storage is local to your application (see use of `NSDocumentDirectory`). While Android have some public folders (i.e. `sdcard/Pictures`), iOS doesn't allow it, so the StorageService implementation produces different results in this case. – José Pereda Feb 04 '17 at 19:25
  • ok. so when i have saved a image with the picturesservice, how can i then get access to the file itself - not as an image with loadImageFromGallery - on ios? can you maybe give me a hint in this direction. – tonimaroni Feb 05 '17 at 09:38
  • why not access through loadimagefromgallery? this will give me an image which would be fine. but i can not convert it to a file/ByteArrayInputStream which i then can upload to a webservice. thats the final result to give maybe some more background informations what needs to be solved. thanks for any help again. – tonimaroni Feb 05 '17 at 09:48

1 Answers1

3

As discussed previously in this question, on mobile there is no Swing or AWT, so once you have a JavaFX image, to retrieve it you can't use SwingFXUtils.

The solution proposed in the mentioned question works on Android as you can easily get the File. On iOS, on the contrary, the file is located in the gallery and the current Charm Down PicturesService doesn't access the gallery.

Instead of modifying that service (with native code like this one), the idea to get a byteArray from the JavaFX Image that you can send to a web service is based on two steps:

  • Get the image pixels as a byte array
  • Encode the byte array into a string.

If you check the iOS implementation for PicturesService::takePhoto, the image from the iOS native layer is sent to the JavaFX layer via Base64 encoding and decoding.

Solution 1

This code snippet works for me:

// Take a photo and add image to imageView
Button button = new Button("Take Photo");
button.setOnAction(e ->
    Services.get(PicturesService.class)
        .ifPresent(s -> s.takePhoto(false).ifPresent(imageView::setImage)));

// Encode image
imageView.imageProperty().addListener((obs, ov, image) -> {
    if (image != null) {
        // 1. image to byte array
        PixelReader pixelReader = image.getPixelReader();
        int width = (int) image.getWidth(); 
        int height = (int) image.getHeight(); 
        byte[] buffer = new byte[width * height * 4]; 
        pixelReader.getPixels(0, 0, width, height, PixelFormat.getByteBgraInstance(), buffer, 0, width * 4); 

        // 2. Encode to String
        String encoded = Base64.getEncoder().encodeToString(buffer);

        // 3. send string...
    }
} 

You can check the process works by reversing these steps and creating an image out of the encoded string:

byte[] imageBytes = Base64.getDecoder().decode(encoded.getBytes(StandardCharsets.UTF_8));

WritablePixelFormat<ByteBuffer> wf = PixelFormat.getByteBgraInstance();
WritableImage writableImage = new WritableImage(width, height);
PixelWriter pixelWriter = writableImage.getPixelWriter();
pixelWriter.setPixels(0, 0, width, height, wf, imageBytes, 0, width * 4);

imageView.setImage(writableImage);

Solution 2

If you want to transform the byte array from PixelReader::getPixels into a BufferedImage, that won't work: the byte arrays are different.

So you need to use something that the buffered image will be able to process.

Having a look at the SwingFXUtils implementation, it uses an int array instead.

So this is another possibility:

// Take a photo and add image to imageView
Button button = new Button("Take Photo");
button.setOnAction(e ->
    Services.get(PicturesService.class)
        .ifPresent(s -> s.takePhoto(false).ifPresent(imageView::setImage)));

// Encode image
imageView.imageProperty().addListener((obs, ov, image) -> {
    if (image != null) {
        // 1. image to int array
        PixelReader pixelReader = image.getPixelReader();
        int width = (int) image.getWidth(); 
        int height = (int) image.getHeight(); 
        int[] data = new int[width * height]; 
        pixelReader.getPixels(0, 0, width, height, PixelFormat.getIntArgbPreInstance(), data, 0, width); 

        // 2. int array to byte array
        ByteBuffer byteBuffer = ByteBuffer.allocate(data.length * 4);        
        IntBuffer intBuffer = byteBuffer.asIntBuffer();
        intBuffer.put(data);


        // 3. Encode to String
        String encoded = Base64.getEncoder().encodeToString(byteBuffer.array());

        // 4. send string...
    }
} 

Now you will have to decode the string, get the int array and create the buffered image:

// 1. Decode string
byte[] imageBytes = Base64.getDecoder().decode(encoded.getBytes(StandardCharsets.UTF_8));

// 2. get int array
ByteBuffer byteBuffer2 = ByteBuffer.wrap(imageBytes);
IntBuffer intBuffer2 = byteBuffer2.asIntBuffer();
int[] imageData = new int[intBuffer2.limit()];
intBuffer2.get(imageData);

// 3. create buffered image
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
bufferedImage.setRGB(0, 0, width, height, imageData, 0, width);
Community
  • 1
  • 1
José Pereda
  • 44,311
  • 7
  • 104
  • 132
  • Thank you for your help! I tried your solution which runs fine - means i can encode and decode again to set the image in the imageView. From my understanding i should be able to do ByteArrayInputStream bis = new ByteArrayInputStream(imageBytes); BufferedImage image = ImageIO.read(bis); but image is null. Whats the missing part to get a BufferedImage? – tonimaroni Feb 06 '17 at 14:02
  • I am asking this because i think Base64 encoding is wrong... i have sent the String to my webserver and decoded it there but without look. So i tried to get a BufferedImage right on gluon-mobile as above but its null (as on the webeservice). Means it can not understand the data it receives. Hoping for a hint and more enlightenment on this. Thanks. – tonimaroni Feb 06 '17 at 19:55
  • I've edited my answer with a new solution that will allow you create a bufferedImage. Let me know if that works for you. – José Pereda Feb 07 '17 at 11:42
  • Hi José. Finally i have solved it with your provided information. Thank you very much!! – tonimaroni Feb 08 '17 at 14:59
  • @JoséPereda if I am retrieving the file from a DB blob, will this method still work? I don’t know the properties of the image (width and height). – A.Sharma Nov 24 '17 at 00:33
  • Nevermind. I stored the width and height in the DB as well and this works wonders. – A.Sharma Nov 24 '17 at 03:34