Support rendering transparent bitmaps in presentations. (#990)

* Support rendering transparent bitmaps in presentations.

Add PictureShape.getAlpha() method and implementations for HSLF and
XSLF. Then make use of it in DrawPictureShape to apply the right alpha
value to the picture being drawn.

Fixed a bug in BitmapImageRenderer that considered alpha value 0 as
"fully opaque", when it means "fully transparent" instead.

Finally, added a test for this feature in TestDrawPictureShape for XSLF.
A test for HSLF could not be created because it was not possible to
generate a test file with today's tools; MS Office removes the
bitmap transparency effect when saving as .ppt, and LibreOffice blends
it into the bitmap.

* Address reviewer comments.

* Add comment about default alpha value.

* Prevent NPE in XSLFPictureShape.getAlpha().

* Change wording in comments to avoid the word "percentage".

* Use static vars for extreme alpha values.
This commit is contained in:
Jacobo Aragunde Pérez 2026-01-17 13:47:09 +01:00 committed by GitHub
parent e3e04a641f
commit 372388b7ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 64 additions and 3 deletions

View File

@ -225,6 +225,14 @@ public class XSLFPictureShape extends XSLFSimpleShape
POIXMLUnits.parsePercent(r.xgetR())); POIXMLUnits.parsePercent(r.xgetR()));
} }
public int getAlpha() {
CTBlip blip = getBlip();
if (blip == null) {
return FULLY_OPAQUE_ALPHA_VALUE;
}
return blip.sizeOfAlphaModFixArray() > 0 ? POIXMLUnits.parsePercent(blip.getAlphaModFixArray(0).xgetAmt()) : FULLY_OPAQUE_ALPHA_VALUE;
}
/** /**
* Add a SVG image reference * Add a SVG image reference
* @param svgPic a previously imported svg image * @param svgPic a previously imported svg image

View File

@ -22,8 +22,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assumptions.assumeFalse; import static org.junit.jupiter.api.Assumptions.assumeFalse;
import java.awt.Dimension; import java.awt.*;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -117,4 +118,33 @@ class TestDrawPictureShape {
return val; return val;
} }
} }
@Test
void testAlphaXSLFPictureShape() throws IOException {
SlideShow<?,?> ss = openSampleDocument("picture-transparency.pptx");
// First slide contains a fully opaque bitmap
verifySlideFirstPixelColor(ss.getSlides().get(0), new Color(0, 0, 0, 255));
// Second slide contains a 20% transparency bitmap (255*0.8=204)
verifySlideFirstPixelColor(ss.getSlides().get(1), new Color(0, 0, 0, 204));
// Third slide contains a 60% transparency bitmap (255*0.4=102)
verifySlideFirstPixelColor(ss.getSlides().get(2), new Color(0, 0, 0, 102));
// Fourth slide contains a fully transparent bitmap
verifySlideFirstPixelColor(ss.getSlides().get(3), new Color(0, 0, 0, 0));
}
private void verifySlideFirstPixelColor(Slide<?,?> slide, Color color) {
PictureShape<?,?> picShape = null;
for (Shape<?,?> shape : slide.getShapes()) {
if (shape instanceof PictureShape) {
picShape = (PictureShape<?,?>)shape;
break;
}
}
assertNotNull(picShape);
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
new DrawPictureShape(picShape).draw(img.createGraphics());
assertEquals(Transparency.TRANSLUCENT, img.getTransparency());
assertEquals(color, new Color(img.getRGB(0, 0), true));
}
} }

View File

@ -228,4 +228,8 @@ public class HSLFPictureShape extends HSLFSimpleShape implements PictureShape<HS
int fixedPoint = prop.getPropertyValue(); int fixedPoint = prop.getPropertyValue();
return Units.fixedPointToDouble(fixedPoint); return Units.fixedPointToDouble(fixedPoint);
} }
}
public int getAlpha() {
return (int)(super.getAlpha(EscherPropertyTypes.FILL__FILLOPACITY)*100000.0);
}
}

View File

@ -289,7 +289,8 @@ public class BitmapImageRenderer implements ImageRenderer {
return new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); return new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
} }
if (alpha == 0) { if (alpha == 1) {
// Do not apply any rescale for a fully opaque alpha value
return image; return image;
} }

View File

@ -36,6 +36,9 @@ import org.apache.poi.sl.usermodel.PictureData;
import org.apache.poi.sl.usermodel.PictureShape; import org.apache.poi.sl.usermodel.PictureShape;
import org.apache.poi.sl.usermodel.RectAlign; import org.apache.poi.sl.usermodel.RectAlign;
import static org.apache.poi.sl.usermodel.PictureShape.FULLY_OPAQUE_ALPHA_VALUE;
import static org.apache.poi.sl.usermodel.PictureShape.FULLY_TRANSPARENT_ALPHA_VALUE;
public class DrawPictureShape extends DrawSimpleShape { public class DrawPictureShape extends DrawSimpleShape {
private static final Logger LOG = PoiLogManager.getLogger(DrawPictureShape.class); private static final Logger LOG = PoiLogManager.getLogger(DrawPictureShape.class);
@ -50,6 +53,7 @@ public class DrawPictureShape extends DrawSimpleShape {
Rectangle2D anchor = getAnchor(graphics, ps); Rectangle2D anchor = getAnchor(graphics, ps);
Insets insets = ps.getClipping(); Insets insets = ps.getClipping();
int alpha = ps.getAlpha();
PictureData[] pics = { ps.getAlternativePictureData(), ps.getPictureData() }; PictureData[] pics = { ps.getAlternativePictureData(), ps.getPictureData() };
for (PictureData data : pics) { for (PictureData data : pics) {
@ -66,6 +70,9 @@ public class DrawPictureShape extends DrawSimpleShape {
ImageRenderer renderer = getImageRenderer(graphics, ct); ImageRenderer renderer = getImageRenderer(graphics, ct);
if (renderer.canRender(ct)) { if (renderer.canRender(ct)) {
renderer.loadImage(dataBytes, ct); renderer.loadImage(dataBytes, ct);
if (FULLY_TRANSPARENT_ALPHA_VALUE <= alpha && alpha < FULLY_OPAQUE_ALPHA_VALUE) {
renderer.setAlpha(alpha/(float) FULLY_OPAQUE_ALPHA_VALUE);
}
renderer.drawImage(graphics, anchor, insets); renderer.drawImage(graphics, anchor, insets);
return; return;
} }

View File

@ -23,6 +23,9 @@ public interface PictureShape<
S extends Shape<S,P>, S extends Shape<S,P>,
P extends TextParagraph<S,P,? extends TextRun> P extends TextParagraph<S,P,? extends TextRun>
> extends SimpleShape<S,P> { > extends SimpleShape<S,P> {
public static final int FULLY_TRANSPARENT_ALPHA_VALUE = 0;
public static final int FULLY_OPAQUE_ALPHA_VALUE = 100000;
/** /**
* Returns the picture data for this picture. * Returns the picture data for this picture.
* *
@ -47,4 +50,12 @@ public interface PictureShape<
* @return the clipping rectangle, which is given in percent in relation to the image width/height * @return the clipping rectangle, which is given in percent in relation to the image width/height
*/ */
Insets getClipping(); Insets getClipping();
/**
* Returns alpha value in a range between 0 and 100000.
* Value 0 represents complete transparency and 100000 represents complete opacity.
* @return alpha value in the range 0..100000.
* @since 6.0.0
*/
int getAlpha();
} }

Binary file not shown.