flyingsaucerproject / flyingsaucer

XML/XHTML and CSS 2.1 renderer in pure Java
Other
2.02k stars 566 forks source link

Fix transparent background of resized base64 encoded images #393

Closed OpenHelios closed 2 months ago

OpenHelios commented 2 months ago

This PR fixes #392.

OpenHelios commented 2 months ago

Here is a minimal example of an image, which contains two pixels, where the first is transparent and the second is red.

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAADklEQVR4XmNgAIL/QAQABQQB/x/WItEAAAAASUVORK5CYII="/>
<img width="50" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAADklEQVR4XmNgAIL/QAQABQQB/x/WItEAAAAASUVORK5CYII="/>

The image is created with

    final BufferedImage bi = new BufferedImage(2, 1, BufferedImage.TYPE_4BYTE_ABGR);
    bi.setRGB(0, 0, 0);
    bi.setRGB(1, 0, 0xFFFF0000);
    final File outputfile = new File("x.png");
    ImageIO.write(bi, "png", outputfile);

and converted with

$ base64 x.png>x.txt

image The first not resized image has got a transparent pixel next to the red dot and shows the same color as the background (for me it's white). The second resized image shows a big black square, which should be transparent instead, next to the big red square.

asolntsev commented 2 months ago

@OpenHelios Sorry, but I still cannot reproduce the problem. I try to generate PDF from your image:

<div style="background-color: lightblue">
    <h1>Hello, world</h1>

    <h2>Step 1</h2>
    <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAADklEQVR4XmNgAIL/QAQABQQB/x/WItEAAAAASUVORK5CYII="/>

    <h2>Step 2</h2>
    <img width="50" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAADklEQVR4XmNgAIL/QAQABQQB/x/WItEAAAAASUVORK5CYII="/>

    <h2>Step 3</h2>
</div>

but I don't observe any black squares:

image
OpenHelios commented 2 months ago

In the related issue #392 I mentioned, that the issue is only related to flying-soucer-core, i.e. generating PDF is not effected. Meanwhile I created a test to reproduce the issue:

package org.xhtmlrenderer.swing;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

import javax.imageio.ImageIO;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.junit.jupiter.api.Test;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

class Java2DRendererTest {

  private static BufferedImage create2PixelTestImage() {
    final BufferedImage img = new BufferedImage(2, 1, BufferedImage.TYPE_4BYTE_ABGR);
    img.setRGB(0, 0, 0);
    img.setRGB(1, 0, 0xFFFF0000);
    return img;
  }

  private static String createImageBase64(final BufferedImage img) throws IOException {
    try (final ByteArrayOutputStream out = new ByteArrayOutputStream()) {
      try (final OutputStream base64out = Base64.getEncoder().wrap(out)) {
        ImageIO.write(img, "png", base64out);
      }
      return out.toString(StandardCharsets.UTF_8);
    }
  }

  private static BufferedImage getImage(final String html, final int widthInPixel)
      throws SAXException, IOException, ParserConfigurationException {
    final Document document;
    try (final InputStream in = new ByteArrayInputStream(html.getBytes(StandardCharsets.UTF_8))) {
      document = DocumentBuilderFactory.newInstance().newDocumentBuilder() //
          .parse(in);
    }
    return new Java2DRenderer(document, widthInPixel).getImage();
  }

  @Test
  void testGetImageWithResizedEmbeddedABGRImage() throws SAXException, IOException, ParserConfigurationException {
    final BufferedImage smallImg = create2PixelTestImage();
    final String base64Img = createImageBase64(smallImg);
    final String html = "<html><body><img width='100' src='data:image/png;base64," + base64Img + "'/></body></html>";
    final BufferedImage htmlImg = getImage(html, 100);
    assertEquals(Color.RED.getRGB(), htmlImg.getRGB(75, 25));
    assertEquals(Color.WHITE.getRGB(), htmlImg.getRGB(25, 25));
  }

}

The last line in the test expects a white pixel in the left area of the generated image, but with version 9.9.4. of flying-sourcer-core a black pixel is created.

asolntsev commented 2 months ago

@OpenHelios Thank for sharing the code. I didn't even know that FS allows to convert html to image :)

But why you didn't want to add this working test to your PR?...

P.S. I did it in the next PR https://github.com/flyingsaucerproject/flyingsaucer/pull/396

OpenHelios commented 2 months ago

Thanks to apply my fix. Next time, I will add a test to my commit. This time I was not sure, if the test from above is necessary. You added also a test for my changed method, which would also be enough to verify, that my change works. As a positive site effect, the original issue is fixed by this change.