8

I want to generate a QR-code image, convert it to PNG and return it as a HTTP response to my client.

To generate the QR-code I use ZXing. I already tested the conversion part by writing using a FileOutputStream with MatrixToImageWriter.writeToStream(...). That works like a charm.

The web framework I am currently using is Spark (Version 1.1.1). The return of the handle(...)-method is set as the response body. What am I doing wrong here?

With the current solution I get The image "http://localhost:4567/qrcode" cannot be displayed because it contains errors when performing the GET-request with Firefox.

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;

import static spark.Spark.get;
import spark.Request;
import spark.Response;
import spark.Route;

import com.google.gson.Gson;

import com.google.common.io.BaseEncoding;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;

public class App {
    public static void main(String[] args) {
        get(new Route("/qrcode") {

            @Override
            public Object handle(Request request, Response response) {
                // Test data
                QrData data = new QrData("test");

                // Data is wrapped in JSON
                String json = new Gson().toJson(data);

                // Transform JSON to QR-code PNG byte string
                String qrString = "";
                try {
                    qrString = generatePngQrCode(json);
                } catch (Exception e) {
                    e.printStackTrace();
                } 

                // Set response parameters
                response.type("image/png");
                response.status(200);

                // Return response body
                return qrString;
            }
        });
    }

    public String generatePngQrCode(String content) throws Exception {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        // ZXing QR-code encoding
        BitMatrix bitMatrix = new QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, 400, 400);

        // Convert to PNG image and write to stream
        MatrixToImageWriter.writeToStream(bitMatrix, "png", outputStream);

        // Encode to Base 64  
        return BaseEncoding.base64().encode(outputStream.toByteArray());
    }
}
Lucas Hoepner
  • 1,437
  • 1
  • 16
  • 21
  • Why are you base64 encoding the byte[]? If you want to return a png, it needs to be the raw bytes. – Brett Okken Jun 07 '14 at 16:22
  • I found several sources implying that the encoding for PNG is supposed to be Base64 (e.g. http://mrbool.com/how-to-convert-image-to-byte-array-and-byte-array-to-image-in-java/25136). I tried returning the `ByteArrayOutputStream` as well as `toByteArray()`, both yield the same result as the string. – Lucas Hoepner Jun 07 '14 at 16:42
  • Can you write directly to response? The spark website currently has doc for 2.0, but indicates you should be able to call response.raw(). – Brett Okken Jun 07 '14 at 17:27
  • That send me off in the right direction. `response.raw().getOutputStream()` returns an `OutputStream` which has to be used in the `MatrixToImageWriter.writeToStream(bitMatrix, "png", outputStream)` call. @Brett If you want to get this as the correct answer feel free to post it as such. – Lucas Hoepner Jun 07 '14 at 19:06

2 Answers2

15

Just went through this. You can write any file/binary data/output stream using the following code:

byte[] bytes = Files.readAllBytes(Paths.get(filePath));         
HttpServletResponse raw = res.raw();

raw.getOutputStream().write(bytes);
raw.getOutputStream().flush();
raw.getOutputStream().close();


return res.raw();
dessalines
  • 6,352
  • 5
  • 42
  • 59
  • 3
    Are you really supposed to flush and close the output stream? The one who has opened it should close it. If this is different, could you explain why? – Suma Oct 19 '16 at 13:29
  • 2
    What about response headers? – MonoThreaded May 20 '18 at 23:22
  • 1
    `res.header("Content-Type", "application/download");` `res.header("Content-Disposition", "attachment; filename="+fileOutName);` These were the headers I supplied that worked well for my purposes. Keep in mind you can use filename to name the file differently for the client than the file is named on your server. – NekoKikoushi May 30 '19 at 14:28
6

Use response.getRaw to obtain an OutputStream that should be used to write the PNG to (using MatrixToImageWriter).

Brett Okken
  • 6,210
  • 1
  • 19
  • 25