@@ -260,6 +260,8 @@ public abstract class Image extends Rectangle {
260260
261261 // member variables
262262 public static final int [] PNGID = {137 , 80 , 78 , 71 , 13 , 10 , 26 , 10 };
263+ private static final int INDEXED_NO_TRANSPARENCY = -1 ;
264+ private static final int INDEXED_UNSUPPORTED_TRANSPARENCY = -2 ;
263265 /**
264266 * a static that is used for attributing a unique id to each image.
265267 */
@@ -828,61 +830,68 @@ public static Image getInstance(java.awt.Image image, java.awt.Color color,
828830 forceBW = true ;
829831 }
830832
831- // Handle indexed color images
832- if (bi .getColorModel () instanceof IndexColorModel && !forceBW ) {
833- IndexColorModel icm = (IndexColorModel ) bi .getColorModel ();
834- int mapSize = icm .getMapSize ();
835- int bitsPerPixel = icm .getPixelSize ();
836-
837- // Ensure bits per pixel is valid (1, 2, 4, or 8)
838- // For PDF indexed images, bpc should be the bits needed to index the palette
839- if (bitsPerPixel > 8 || bitsPerPixel == 0 ) {
840- bitsPerPixel = 8 ;
841- } else if (bitsPerPixel > 4 ) {
842- bitsPerPixel = 8 ;
843- } else if (bitsPerPixel > 2 ) {
844- bitsPerPixel = 4 ;
845- } else if (bitsPerPixel > 1 ) {
846- bitsPerPixel = 2 ;
847- } else {
848- bitsPerPixel = 1 ;
849- }
833+ // Handle indexed color images when transparency can be represented directly.
834+ // More complex alpha (for example semi-transparency) must fallback to the generic RGB+SMask path below
835+ if (bi .getColorModel () instanceof IndexColorModel icm && !forceBW && color == null ) {
836+ int transparentIndex = getIndexedTransparentPaletteIndex (icm );
837+ if (transparentIndex != INDEXED_UNSUPPORTED_TRANSPARENCY ) {
838+ int mapSize = icm .getMapSize ();
839+ int bitsPerPixel = icm .getPixelSize ();
840+
841+ // Ensure bits per pixel is valid (1, 2, 4, or 8)
842+ // For PDF indexed images, bpc should be the bits needed to index the palette
843+ if (bitsPerPixel > 8 || bitsPerPixel == 0 ) {
844+ bitsPerPixel = 8 ;
845+ } else if (bitsPerPixel > 4 ) {
846+ bitsPerPixel = 8 ;
847+ } else if (bitsPerPixel > 2 ) {
848+ bitsPerPixel = 4 ;
849+ } else if (bitsPerPixel > 1 ) {
850+ bitsPerPixel = 2 ;
851+ } else {
852+ bitsPerPixel = 1 ;
853+ }
850854
851- // Extract palette data
852- byte [] reds = new byte [mapSize ];
853- byte [] greens = new byte [mapSize ];
854- byte [] blues = new byte [mapSize ];
855- icm .getReds (reds );
856- icm .getGreens (greens );
857- icm .getBlues (blues );
858-
859- // Build palette as RGB byte array
860- byte [] palette = new byte [mapSize * 3 ];
861- for (int i = 0 ; i < mapSize ; i ++) {
862- palette [i * 3 ] = reds [i ];
863- palette [i * 3 + 1 ] = greens [i ];
864- palette [i * 3 + 2 ] = blues [i ];
865- }
855+ // Extract palette data
856+ byte [] reds = new byte [mapSize ];
857+ byte [] greens = new byte [mapSize ];
858+ byte [] blues = new byte [mapSize ];
859+ icm .getReds (reds );
860+ icm .getGreens (greens );
861+ icm .getBlues (blues );
862+
863+ // Build palette as RGB byte array
864+ byte [] palette = new byte [mapSize * 3 ];
865+ for (int i = 0 ; i < mapSize ; i ++) {
866+ palette [i * 3 ] = reds [i ];
867+ palette [i * 3 + 1 ] = greens [i ];
868+ palette [i * 3 + 2 ] = blues [i ];
869+ }
870+
871+ // Extract pixel indices
872+ int width = bi .getWidth ();
873+ int height = bi .getHeight ();
874+ byte [] pixelData = generateIndexedColorPixelData (width , bitsPerPixel , height , bi .getRaster ());
875+ // Create indexed image with palette
876+ Image img = Image .getInstance (width , height , 1 , bitsPerPixel , pixelData );
877+
878+ // Set up indexed colorspace: [/Indexed /DeviceRGB maxIndex palette]
879+ PdfArray indexed = new PdfArray ();
880+ indexed .add (PdfName .INDEXED );
881+ indexed .add (PdfName .DEVICERGB );
882+ indexed .add (new PdfNumber (mapSize - 1 ));
883+ indexed .add (new PdfString (palette ));
884+
885+ PdfDictionary additional = new PdfDictionary ();
886+ additional .put (PdfName .COLORSPACE , indexed );
887+ img .setAdditional (additional );
888+ if (transparentIndex >= 0 ) {
889+ img .setTransparency (new int []{transparentIndex , transparentIndex });
890+ }
866891
867- // Extract pixel indices
868- int width = bi .getWidth ();
869- int height = bi .getHeight ();
870- byte [] pixelData = generateIndexedColorPixelData (width , bitsPerPixel , height , bi .getRaster ());
871- // Create indexed image with palette
872- Image img = Image .getInstance (width , height , 1 , bitsPerPixel , pixelData );
873-
874- // Set up indexed colorspace: [/Indexed /DeviceRGB maxIndex palette]
875- PdfArray indexed = new PdfArray ();
876- indexed .add (PdfName .INDEXED );
877- indexed .add (PdfName .DEVICERGB );
878- indexed .add (new PdfNumber (mapSize - 1 ));
879- indexed .add (new PdfString (palette ));
880-
881- PdfDictionary additional = new PdfDictionary ();
882- additional .put (PdfName .COLORSPACE , indexed );
883- img .setAdditional (additional );
884-
885- return img ;
892+ return img ;
893+ }
894+ // Unsupported indexed transparency falls through to the generic RGB+SMask path below.
886895 }
887896 }
888897
@@ -1099,6 +1108,21 @@ private static byte[] generateIndexedColorPixelData(int width, int bitsPerPixel,
10991108 return pixelData ;
11001109 }
11011110
1111+ private static int getIndexedTransparentPaletteIndex (IndexColorModel colorModel ) {
1112+ int transparentIndex = INDEXED_NO_TRANSPARENCY ;
1113+ for (int index = 0 ; index < colorModel .getMapSize (); index ++) {
1114+ int alpha = colorModel .getAlpha (index );
1115+ if (alpha == 0xFF ) {
1116+ continue ;
1117+ }
1118+ if (alpha != 0x00 || transparentIndex != INDEXED_NO_TRANSPARENCY ) {
1119+ return INDEXED_UNSUPPORTED_TRANSPARENCY ;
1120+ }
1121+ transparentIndex = index ;
1122+ }
1123+ return transparentIndex ;
1124+ }
1125+
11021126 /**
11031127 * Packs a single pixel index into the target byte array based on specified bit depth (PDF MSB-first rule).
11041128 */
0 commit comments