Skip to content

Commit 044098f

Browse files
author
Kanstantsin Valeitsenak
committed
Add PDIndexed PDColorSpace support
1 parent acf6425 commit 044098f

2 files changed

Lines changed: 193 additions & 3 deletions

File tree

library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDColorSpace.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
*
3737
* @author John Hewson
3838
* @author Ben Litchfield
39+
* @modifiedBy Kanstantsin Valeitsenak
3940
*/
4041
public abstract class PDColorSpace implements COSObjectable
4142
{
@@ -186,9 +187,7 @@ else if (name == COSName.DEVICEN)
186187
}
187188
else if (name == COSName.INDEXED)
188189
{
189-
// return new PDIndexed(array);
190-
Log.e("PdfBox-Android", "Unsupported color space kind: " + name + ". Will try DeviceRGB instead");
191-
return PDDeviceRGB.INSTANCE;
190+
return new PDIndexed(array);
192191
}
193192
else if (name == COSName.SEPARATION)
194193
{
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package com.tom_roush.pdfbox.pdmodel.graphics.color;
19+
20+
import android.graphics.Bitmap;
21+
import android.os.Build;
22+
23+
import com.tom_roush.pdfbox.cos.COSArray;
24+
import com.tom_roush.pdfbox.cos.COSBase;
25+
import com.tom_roush.pdfbox.cos.COSStream;
26+
import com.tom_roush.pdfbox.cos.COSString;
27+
import com.tom_roush.pdfbox.cos.COSNumber;
28+
import com.tom_roush.pdfbox.cos.COSName;
29+
30+
import java.io.ByteArrayOutputStream;
31+
import java.io.IOException;
32+
import java.io.InputStream;
33+
34+
/**
35+
* Indexed color spaces allow a PDF to use a small color lookup table (palette),
36+
* mapping index values to colors in a base color space (such as DeviceRGB).
37+
* This is useful for reducing file size when only a limited number of colors are used.
38+
*
39+
* The Indexed color space is defined by a base color space, a high value (maximum index),
40+
* and a lookup table containing the actual color values.
41+
*
42+
* Example in PDF: [/Indexed /DeviceRGB 255 <...palette bytes...>]
43+
*
44+
* @author Kanstantsin Valeitsenak
45+
*/
46+
47+
public class PDIndexed extends PDColorSpace
48+
{
49+
private final PDColorSpace baseColorSpace;
50+
private final int highVal;
51+
private final byte[] lookupData;
52+
53+
public PDIndexed(COSArray indexedArray) throws IOException
54+
{
55+
this.array = indexedArray;
56+
57+
COSBase base = array.getObject(1);
58+
this.baseColorSpace = PDColorSpace.create(base);
59+
60+
COSNumber hivalObj = (COSNumber) array.getObject(2);
61+
this.highVal = hivalObj.intValue();
62+
63+
COSBase lookup = array.getObject(3);
64+
65+
if (lookup instanceof COSString)
66+
{
67+
this.lookupData = ((COSString) lookup).getBytes();
68+
}
69+
else if (lookup instanceof COSStream)
70+
{
71+
try (InputStream is = ((COSStream) lookup).createInputStream())
72+
{
73+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
74+
this.lookupData = is.readAllBytes(); // API 26+
75+
} else {
76+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
77+
byte[] buffer = new byte[1024];
78+
int read;
79+
while ((read = is.read(buffer)) != -1) {
80+
baos.write(buffer, 0, read);
81+
}
82+
this.lookupData = baos.toByteArray();
83+
}
84+
}
85+
}
86+
else
87+
{
88+
throw new IOException("Invalid lookup data in Indexed color space");
89+
}
90+
}
91+
92+
@Override
93+
public String getName()
94+
{
95+
return COSName.INDEXED.getName();
96+
}
97+
98+
@Override
99+
public int getNumberOfComponents()
100+
{
101+
return 1;
102+
}
103+
104+
@Override
105+
public float[] getDefaultDecode(int bitsPerComponent)
106+
{
107+
return new float[] { 0, highVal };
108+
}
109+
110+
@Override
111+
public PDColor getInitialColor()
112+
{
113+
return new PDColor(new float[] { 0 }, this);
114+
}
115+
116+
@Override
117+
public float[] toRGB(float[] value)
118+
{
119+
int index = Math.round(value[0]);
120+
int numColorComponents = baseColorSpace.getNumberOfComponents();
121+
int paletteIndex = index * numColorComponents;
122+
123+
if (paletteIndex + numColorComponents > lookupData.length)
124+
{
125+
return baseColorSpace.getInitialColor().getComponents(); // fallback
126+
}
127+
128+
float[] rgb = new float[3];
129+
float[] component = new float[numColorComponents];
130+
for (int i = 0; i < numColorComponents; i++)
131+
{
132+
int byteVal = lookupData[paletteIndex + i] & 0xFF;
133+
component[i] = byteVal / 255f;
134+
}
135+
136+
try {
137+
rgb = baseColorSpace.toRGB(component);
138+
} catch (IOException e) {
139+
throw new RuntimeException(e);
140+
}
141+
return rgb;
142+
}
143+
144+
@Override
145+
public Bitmap toRGBImage(Bitmap raster) throws IOException {
146+
if (raster.getConfig() != Bitmap.Config.ALPHA_8)
147+
{
148+
throw new IOException("Expected ALPHA_8 bitmap as Indexed source.");
149+
}
150+
151+
int width = raster.getWidth();
152+
int height = raster.getHeight();
153+
154+
Bitmap rgbBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
155+
156+
int[] pixels = new int[width];
157+
for (int y = 0; y < height; y++)
158+
{
159+
raster.getPixels(pixels, 0, width, 0, y, width, 1);
160+
161+
for (int x = 0; x < width; x++)
162+
{
163+
int index = pixels[x] & 0xFF;
164+
165+
int baseIndex = index * baseColorSpace.getNumberOfComponents();
166+
167+
if (baseIndex + 2 >= lookupData.length)
168+
{
169+
rgbBitmap.setPixel(x, y, android.graphics.Color.BLACK); // fallback
170+
continue;
171+
}
172+
173+
float[] baseColor = new float[baseColorSpace.getNumberOfComponents()];
174+
for (int i = 0; i < baseColor.length; i++)
175+
{
176+
baseColor[i] = (lookupData[baseIndex + i] & 0xFF) / 255f;
177+
}
178+
179+
float[] rgb = baseColorSpace.toRGB(baseColor);
180+
181+
int r = (int)(rgb[0] * 255);
182+
int g = (int)(rgb[1] * 255);
183+
int b = (int)(rgb[2] * 255);
184+
185+
rgbBitmap.setPixel(x, y, android.graphics.Color.rgb(r, g, b));
186+
}
187+
}
188+
189+
return rgbBitmap;
190+
}
191+
}

0 commit comments

Comments
 (0)