Search code examples
javaspringspring-bootimagethymeleaf

How to display a BufferedImage in Thymeleaf


I'm currently trying to teach myself Spring Boot by writing a simple tabletop simulator for the popular card game Magic the Gathering. So what I want to do first is retrieve card images from the Scryfall API and display them in Thymeleaf. I know how to do this for static images, but I can't seem to find a way to display images retrieved dynamically. My current workaround is to provide Thymeleaf with the Scryfall URI, but what I really want to do is to display a BufferedImage in Thymeleaf. So here's my current code for the controller.

package mtg;

import java.util.Map;

import org.springframework.boot.json.BasicJsonParser;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

@Controller
@RequestMapping("/sample")
public class SampleCardController {

@ModelAttribute
public void addCardToModel(Model model) {
    RestTemplate rest = new RestTemplate();
    String jsonString = rest.getForObject(
            "https://api.scryfall.com/cards/random", String.class);
    BasicJsonParser parser = new BasicJsonParser();
    Map<String, Object> map = parser.parseMap(jsonString);
    String name = (String) map.get("name");
    String uri = (String) map.get("uri");
    model.addAttribute("cardName", name);
    model.addAttribute("imageURI", uri + "?format=image");
}

@GetMapping
public String showSampleCard() {
    return "sample";
}
}

And this is the Thymeleaf template sample.html:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
  <head>
    <title>Sample</title>
  </head>
  <body>
    <h1>Here's a sample card!</h1>
    <h3 th:text="${cardName}"></h3>
    <img th:src="${imageURI}"/>
  </body>
</html>

What I really want to be doing in the controller, is something like this:

package mtg;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;

import javax.imageio.ImageIO;

import org.springframework.boot.json.BasicJsonParser;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

@Controller
@RequestMapping("/sample")
public class SampleCardController2 {

    @ModelAttribute
    public void addCardToModel(Model model) {
        RestTemplate rest = new RestTemplate();
        String jsonString = rest.getForObject(
                "https://api.scryfall.com/cards/random", String.class);
        BasicJsonParser parser = new BasicJsonParser();
        Map<String, Object> map = parser.parseMap(jsonString);
        String name = (String) map.get("name");
        String imageURI = (String) map.get("uri");
        BufferedImage image = null;
        try {
            image = ImageIO.read(new URL(imageURI + "?format=image"));
        } catch (MalformedURLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        model.addAttribute("cardName", name);
        model.addAttribute("image", image);
    }

    @GetMapping
    public String showSampleCard() {
        return "sample";
    }

}

But I don't know how to get Thymeleaf to display the image. It seems that for the img tag`you can only provide a th:src attribute which needs an URL. Is there something similar to th:text="${cardName}" for images where you can use the name of the model attribute?

EDIT: See @Lee Greiner 's comment below for how to fix the template.


Solution

  • You can insert more than just an URL in the img tag. In this case, you can insert the base64 representation of the image you are loading by doing this:

    <div>
      <p>Taken from wikpedia</p>
      <img src="data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUA
        AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
            9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red dot" />
    </div>
    

    And from your code, get the base64 from the BefferedImage object like this:

    public static String imgToBase64String(final RenderedImage img, final String formatName)
    {
      final ByteArrayOutputStream os = new ByteArrayOutputStream();
    
      try
      {
        ImageIO.write(img, formatName, os);
        return Base64.getEncoder().encodeToString(os.toByteArray());
      }
      catch (final IOException ioe)
      {
        throw new UncheckedIOException(ioe);
      }
    }
    

    And pass it to the view.

    Links IMG tag base64 src

    BufferedImage to base64