B4J Question [SOLVED] How to create JPEG output stream

JackKirk

Well-Known Member
Licensed User
I need to be able to create a JPEG output stream in B4J so I can save images arriving via FTP in AWS S3 buckets as JPEGs (PNG doesn't cut it).

In B4A I was doing this without problems with code of form:

....WriteToStream(wrk_out, jpg_quality, "JPEG")

B4J apparently doesn't support JPEG (although B4A does - another of life's little mysteries).

I have been playing around with code I found here:

http://www.java2s.com/Code/Java/2D-...fileTheJPEGqualitycanbespecifiedinpercent.htm

So far I have:
B4X:
Private Sub WriteToStreamJPEG(In_image As Image, JPEG_quality As Int) As OutputStream
   
    Private wrk_out As OutputStream
   
    wrk_out.InitializeToBytesArray(0)
   
    nativeMe.RunMethod("saveImageAsJPEG", Array(In_image, wrk_out, JPEG_quality))
'    nativeMe.RunMethod("saveImageAsJPEG", Array(Array As Image (In_image), Array As OutputStream (wrk_out), Array As Int (JPEG_quality)))
   
    Return wrk_out
   
End Sub

#If Java

//See http://www.java2s.com/Code/Java/2D-Graphics-GUI/WritesanimagetoanoutputstreamasaJPEGfileTheJPEGqualitycanbespecifiedinpercent.htm

    import java.awt.Graphics;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.util.Iterator;
    import java.util.Locale;

    import javax.imageio.IIOImage;
    import javax.imageio.ImageIO;
    import javax.imageio.ImageReader;
    import javax.imageio.ImageWriteParam;
    import javax.imageio.ImageWriter;
    import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
    import javax.imageio.stream.ImageInputStream;
    import javax.imageio.stream.ImageOutputStream;

    /**
    * Writes an image to an output stream as a JPEG file. The JPEG quality can
    * be specified in percent.
    *
    * @param image
    *            image to be written
    * @param stream
    *            target stream
    * @param qualityPercent
    *            JPEG quality in percent
    *
    * @throws IOException
    *             if an I/O error occured
    * @throws IllegalArgumentException
    *             if qualityPercent not between 0 and 100
    */
    public static void saveImageAsJPEG(BufferedImage image,
            OutputStream stream, int qualityPercent) throws IOException {
        if ((qualityPercent < 0) || (qualityPercent > 100)) {
            throw new IllegalArgumentException("Quality out of bounds!");
        }
        float quality = qualityPercent / 100f;
        ImageWriter writer = null;
        Iterator iter = ImageIO.getImageWritersByFormatName("jpg");
        if (iter.hasNext()) {
            writer = (ImageWriter) iter.next();
        }
        ImageOutputStream ios = ImageIO.createImageOutputStream(stream);
        writer.setOutput(ios);
        ImageWriteParam iwparam = new JPEGImageWriteParam(Locale.getDefault());
        iwparam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
        iwparam.setCompressionQuality(quality);
        writer.write(null, new IIOImage(image, null, null), iwparam);
        ios.flush();
        writer.dispose();
        ios.close();
    }

#End If
Which dies with:

Waiting for debugger to connect...
Program started.
Error occurred on line: 1381 (Main)
java.lang.RuntimeException: Method: saveImageAsJPEG not matched.
at anywheresoftware.b4j.object.JavaObject.RunMethod(JavaObject.java:129)
at b4j.example.main._writetostreamjpeg(main.java:2695)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:613)
at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:231)
at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:159)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:90)
at anywheresoftware.b4a.ShellBA.raiseEvent2(ShellBA.java:93)
at anywheresoftware.b4a.debug.Debug.delegate(Debug.java:61)
at b4j.example.main$ResumableSub_Photo_uploader.resume(main.java:520)
at b4j.example.main._photo_uploader(main.java:453)
at b4j.example.main._event_obj_check_backlog_timer_tick(main.java:432)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:613)
at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:231)
at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:159)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:90)
at anywheresoftware.b4a.ShellBA.raiseEvent2(ShellBA.java:93)
at anywheresoftware.b4a.objects.Timer$TickTack$1.run(Timer.java:118)
at com.sun.javafx.application.PlatformImpl.lambda$null$173(PlatformImpl.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(PlatformImpl.java:294)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
at java.lang.Thread.run(Thread.java:745)
Extensive googling would suggest my problem is trying to pass a B4J Image to a java BufferedImage.

I can find no way around this and I really need to get it resolved - any and all help appreciated...
 

JackKirk

Well-Known Member
Licensed User
Erel,

Yes I saw that thread when I was researching the problem.

There is no way in that thread, that I can see, to adjust the jpeg quality and hence reduce file size - which is the whole reason for wanting to use it.

It seems to me that I just need a mechanism to convert B4J Image to java BufferedImage - any thoughts.

BTW I have seen the BufferedImage stuff in the jCoreExtras library but can not see how or if it is relevant or usable.

Again all help appreciated...
 
Last edited:

OliverA

Expert
Licensed User
If you are receiving the image via ftp, why not just treat it as a binary file and then upload it to AWS that way. No need to treat the file as an image.
 

JackKirk

Well-Known Member
Licensed User
I have to do some processing after receiving the files via FTP - generate thumbnails, do facial recognition, define watermark locations amongst others...

I control the JPG quality of the FTP stuff at the cameras and want the ability to have different (lower) JPG qualities of the final AWS S3 files - reason being that facial recognition is better with higher quality but higher quality final images means unnecessarily slower downloading to ultimate customer.

I really need some help with the original problem....
 

OliverA

Expert
Licensed User

OliverA

Expert
Licensed User

Daestrum

Well-Known Member
Licensed User
I think, if you are passing an image to your java code you need to use
B4X:
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.image.Image;
Change the method definition so it requires an Image
B4X:
  public static void saveImageAsJPEG(Image image,
                        OutputStream stream, int qualityPercent) throws IOException {
Then call immediately on entry to create the buffered image (bufImg)
B4X:
  BufferedImage bufImg = SwingFXUtils.fromFXImage(image,null);
Then you can pass that to your writer
B4X:
...
writer.write(null, new IIOImage(bufImg, null, null), iwparam);
...
**not tested but pretty sure that will solve it as it compiles with no errors.
 

JackKirk

Well-Known Member
Licensed User
Curiosity: How are you achieving this? Within B4J? Using external tools?
Oliver,

Firstly thank you for your interest in my problem.

As regards the facial recognition I fear I should have omitted reference to it - this is a major project and I am obliged to be quiet. I will say that as of today it would appear we have every major hurdle crossed and it is working beautifully - I will elaborate in the "my creations" forums when we go public in a couple of months.
 

JackKirk

Well-Known Member
Licensed User
I think, if you are passing an image to your java code you need to use
B4X:
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.image.Image;
Change the method definition so it requires an Image
B4X:
public static void saveImageAsJPEG(Image image,
                        OutputStream stream, int qualityPercent) throws IOException {
Then call immediately on entry to create the buffered image (bufImg)
B4X:
BufferedImage bufImg = SwingFXUtils.fromFXImage(image,null);
Then you can pass that to your writer
B4X:
...
writer.write(null, new IIOImage(bufImg, null, null), iwparam);
...
**not tested but pretty sure that will solve it as it compiles with no errors.
Daestrum,

I can not thank you enough for your response, although it was not the whole answer it set me on the path to the final solution which I outline below:

TO GENERATE A JPEG OUTPUT STREAM WITH CONTROL OVER JPEG QUALITY YOU CAN USE THIS CODE

B4X:
Private Sub WriteToStreamJPEG(In_image As Image, JPEG_quality As Int) As OutputStream

    Private wrk_out As OutputStream

    wrk_out.InitializeToBytesArray(0)

    joMe.RunMethod("saveImageAsJPEG", Array(In_image, wrk_out, JPEG_quality))

    Return wrk_out

End Sub

#If Java

//See http://www.java2s.com/Code/Java/2D-Graphics-GUI/WritesanimagetoanoutputstreamasaJPEGfileTheJPEGqualitycanbespecifiedinpercent.htm
//and https://stackoverflow.com/questions/19548363/image-saved-in-javafx-as-jpg-is-pink-toned
//and https://www.b4x.com/android/forum/threads/how-to-create-jpeg-output-stream.83830/#post-531118

    import java.awt.image.BufferedImage;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.util.Iterator;
    import java.util.Locale;

    import javafx.scene.image.Image;
    import javafx.embed.swing.SwingFXUtils;
    import java.awt.Graphics2D;

    import javax.imageio.IIOImage;
    import javax.imageio.ImageIO;
    import javax.imageio.ImageWriteParam;
    import javax.imageio.ImageWriter;
    import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
    import javax.imageio.stream.ImageOutputStream;

    /**
    * Writes an image to an output stream as a JPEG file. The JPEG quality can
    * be specified in percent.
    *
    * @param image
    *            image to be written
    * @param stream
    *            target stream
    * @param qualityPercent
    *            JPEG quality in percent
    *
    * @throws IOException
    *             if an I/O error occured
    * @throws IllegalArgumentException
    *             if qualityPercent not between 0 and 100
    */
    public static void saveImageAsJPEG(Image img,
            OutputStream stream, int qualityPercent) throws IOException {
        if ((qualityPercent < 0) || (qualityPercent > 100)) {
            throw new IllegalArgumentException("Quality out of bounds!");
        }

        // Get buffered image
        BufferedImage buffimg = SwingFXUtils.fromFXImage(img, null);

        // Remove alpha-channel from buffered image
        BufferedImage imageRGB = new BufferedImage(buffimg.getWidth(), buffimg.getHeight(), BufferedImage.OPAQUE);
        Graphics2D graphics = imageRGB.createGraphics();
        graphics.drawImage(buffimg, 0, 0, null);
        graphics.dispose();
    
        float quality = qualityPercent / 100f;
        ImageWriter writer = null;
        Iterator iter = ImageIO.getImageWritersByFormatName("jpg");
        if (iter.hasNext()) {
            writer = (ImageWriter) iter.next();
        }
        ImageOutputStream ios = ImageIO.createImageOutputStream(stream);
        writer.setOutput(ios);
        ImageWriteParam iwparam = new JPEGImageWriteParam(Locale.getDefault());
        iwparam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
        iwparam.setCompressionQuality(quality);
        writer.write(null, new IIOImage(imageRGB, null, null), iwparam);
        ios.flush();
        writer.dispose();
        ios.close();
    }

#End If
I found after adding Daestrum's contribution that it all worked fine - except when you supplied it with certain images that had been resized.

Sometimes I would get a result which was washed with a reddish tinge - this may be a symptom of Erel's comment "all kinds of color issues with the JPEG encoder" in this post:

https://www.b4x.com/android/forum/threads/save-canvas-in-jpg.42839/#post-259532

A day and a lot of googling, groping and frustration I finally found:

https://stackoverflow.com/questions/19548363/image-saved-in-javafx-as-jpg-is-pink-toned

which provided a fairly straight forward solution (removing the alpha channel) - I inserted that and now all is rosy (well actually all is not rosy :p:p:p)

Thanks again...
 
Last edited:

OliverA

Expert
Licensed User
images arriving via FTP in AWS S3 buckets as JPEGs
adjust the jpeg quality and hence reduce file size
It looks like you already found your solution (via inline Java code) and my previous question was more of a fishing expedition to see if external tools would or would not fit into your project. Since you are already using S3 and depending how much resources your project is going to expend in resizing various JPEGs, I just wanted to point out another method, using the Lambda and API services provided by Amazon/AWS. This may not fit your project, but could be a way to off-load some processing away from your resources to the seemingly endless (depending on wallet, of course) resources of AWS.

Resize Images on the Fly with Amazon S3, AWS Lambda, and Amazon API Gateway
 

JackKirk

Well-Known Member
Licensed User
Oliver,

At the moment we are staying with AWS EC2 Windows instances - mainly because of a long comfortable exposure to Wintel.

We are aware of Lambda and have it marked for future exploration but right now the heat is on to get up and running so we are sticking with what we know best.

Thanks for the link - that looks like it could be very useful - I will digest it this evening.

Have you actually written Lambda stuff with B4J?
 

OliverA

Expert
Licensed User
Have you actually written Lambda stuff with B4J?
Sadly, no. The only AWS exposure I have so far is in testing Beanstalk (using Amazon's OS image) and RDS with B4J Server/ABMaterial. I've been looking into S3 in the past and happen to run into the whole picture scaling information during that time and so when you mentioned S3, JPEGs and picture scaling, I thought this (Lambda/S3/API) info may be relevant/interesting to you.
 
Top