Working with Other Components in an Extension

This is what I have so far (not working)

   @SimpleFunction(description = "Get Image and returns base64 string")
    public void GetImageAsBase64(final Image image ) throws Exception {

      final View imageView = image.getView();
      Bitmap imageBitmap = ((BitmapDrawable)imageView.getDrawable()).getBitmap();

      String encodedString = bitMapToBase64(imageBitmap);    
    }

    public String bitMapToBase64(Bitmap bitMap) {
      ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
      bitMap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream);
      byte[] byteArray = byteArrayOutputStream.toByteArray();
      return Base64.encodeToString(byteArray, Base64.DEFAULT);
    }

Edit: Its actually because you are using View instead of the Image View.

So when you access the Image.getView method, you additionally have to cast it to its ImageView class. The getDrawable method is only on ImageView.

Something like this

final ImageView imageView = (ImageView) imageComponent.getView();
final Bitmap bitmap1 = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
1 Like

That just gives me more unresolvables :frowning:

image

For the first and second errors resulted in by theImageView symbol, it appears the android.view.ImageView class wasn't imported, importing it should get it fixed.
For the second error, i think the imageComponent variable in @Kumaraswamy's code is equivalent to the image variable in your code, renaming imageComponent to image should work.
The fourth error should be resolved by applying the first fix.

2 Likes

Thank you Mohamed, that clears the errors.

Now need to test.

2 Likes

Success!!

image

JAVA
import android.graphics.Bitmap;
import android.graphics.drawable.*;
import android.util.Base64;
import android.widget.*;
import com.google.appinventor.components.annotations.SimpleEvent;
import com.google.appinventor.components.annotations.SimpleFunction;
import com.google.appinventor.components.runtime.*;
import java.io.ByteArrayOutputStream;


public class ImageBase64 extends AndroidNonvisibleComponent {

    public ImageBase64(ComponentContainer container) {
      super(container.$form());

    }

    @SimpleFunction(description = "Get Image and returns base64 string")
    public void GetImageAsBase64(Image image ) throws Exception {

      final ImageView imageView = (ImageView) image.getView();
      Bitmap imageBitmap = ((BitmapDrawable)imageView.getDrawable()).getBitmap();

      String encodedString = bitMapToBase64(imageBitmap);
      AfterImageBase64(encodedString);
    }

    public String bitMapToBase64(Bitmap bitMap) {
      ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
      bitMap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream);
      byte[] byteArray = byteArrayOutputStream.toByteArray();
      return Base64.encodeToString(byteArray, Base64.DEFAULT);
    }

    @SimpleEvent(description = "Called after the image is converted. The text argument `imageBase64Data` is the base64 string of the image")
    public void AfterImageBase64(String imageBase64Data) {
      EventDispatcher.dispatchEvent(this, "AfterImageBase64", imageBase64Data);
    }
  }

3 Likes

You will be please to here (if not amazed, given my poor showing so far....) that i have been able to successfully do this part myself:

JAVA

@SimpleFunction(description="Set Image component with image from base64 string")
    public void SetImageFromBase64(final Image image, String base64String) {
        final ImageView imageView = (ImageView) image.getView();
        byte[] decodedString = Base64.decode(base64String, Base64.DEFAULT);
        Bitmap decodedByte = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);
        imageView.setImageBitmap(decodedByte);
    }

BLOCKS
image

SCREENSHOT

A heartfelt thanks to everyone who has participated, I certainly learnt a lot, and hope it has been useful to others too.

5 Likes

I'd suggest not doing this via reflection, to be honest. We make no guarantees about whether private methods will be retained release to release. Only the public methods are expected to remain stable. Secondly, in systems where proguard is applied to the final app, the method may be renamed or inlined so reflection will fail.

1 Like

Could you expand.....perhaps offer alternatives where reflection / private methods have been used ?

1 Like

Or maybe it's better to add base64 method to all components that display images and that save images? I think it would be an easy project and there would be no backward compatibility issues.

Not just images....

There is the soundRecorder and the camCorder too, we could then get carried away and consider the Player and VideoPlayer as well :wink:

Can sounds also base64 encoded? I think if someone encodes a long sound file that is a few MBs, could it be resource consuming?

It can be up to 30% bigger, but as long as the device can cope....

See method here by @Juan_Antonio (& me...)

Maybe but, there should be a method for extensions to work with the Canvas?

Edit: I created a small PR here.

1 Like

@Kumaraswamy

This appears to have stopped working with the last release (n195 or something Google)

My code in extension:

@SimpleFunction(description = "Get Canvas Image and returns base64 string")
  public void GetCanvasImageAsBase64(final Canvas canvas ) throws Exception {
    final View canvasView = (View) canvas.getView();
    final Method method = canvasView.getClass().getDeclaredMethod("buildCache");
    method.setAccessible(true);
    Bitmap imageBitmap = (Bitmap) method.invoke(canvasView);
    String encodedString = bitMapToBase64(imageBitmap);
    AfterCanvasBase64(encodedString);
  }

image

That's why reflection is a bit unstable over time.

@SimpleFunction(description = "Get Canvas Image and returns base64 string")
  public void GetCanvasImageAsBase64(final Canvas canvas ) throws Exception {
    Bitmap imageBitmap = canvas.getBitmap();
    String encodedString = bitMapToBase64(imageBitmap);
    AfterCanvasBase64(encodedString);
  }

Calling .getBitmap() should be enough.

But... since I believe the extension build sources are not yet updated, you should use this code instead:

@SimpleFunction(description = "Get Canvas Image and returns base64 string")
  public void GetCanvasImageAsBase64(final Canvas canvas) throws Exception {
    final Method method = canvas.getClass().getDeclaredMethod("getBitmap");
    Bitmap imageBitmap = (Bitmap) method.invoke(canvas);
    String encodedString = bitMapToBase64(imageBitmap);
    AfterCanvasBase64(encodedString);
  }

This one, I can assure you, will not break in future.

Did you mean to leave out this:

final View canvasView = canvas.getView();

???

1 Like

That's not required anymore, the code I posted will work.

Thanks so much, just testing....

Works just fine, if a bit slow (using companion), but it works which is the main thing.

Many thanks again

:smiley:

1 Like