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 package org.apache.commons.imaging.formats.tiff.photometricinterpreters;
18
19 import java.io.IOException;
20 import java.util.Arrays;
21
22 import org.apache.commons.imaging.ImagingException;
23 import org.apache.commons.imaging.ImagingFormatException;
24 import org.apache.commons.imaging.common.AllocationRequestException;
25 import org.apache.commons.imaging.common.Allocator;
26 import org.apache.commons.imaging.common.ImageBuilder;
27
28 public final class PhotometricInterpreterPalette extends AbstractPhotometricInterpreter {
29
30 /**
31 * The color map of integer ARGB values tied to the pixel index of the palette.
32 */
33 private final int[] indexColorMap;
34 private final int bitsPerPixelMask;
35
36 /**
37 * Constructs a new instance.
38 *
39 * @param samplesPerPixel Samples per pixel.
40 * @param bitsPerSample Bits per sample.
41 * @param predictor TODO
42 * @param width TODO
43 * @param height TODO
44 * @param colorMap TODO
45 * @throws ImagingFormatException if an index into the {@code colorMap} is out of bounds.
46 * @throws AllocationRequestException Thrown when an allocation request exceeds the {@link Allocator} limit.
47 */
48 public PhotometricInterpreterPalette(final int samplesPerPixel, final int[] bitsPerSample, final int predictor, final int width, final int height,
49 final int[] colorMap) {
50 super(samplesPerPixel, bitsPerSample, predictor, width, height);
51
52 final int bitsPerPixel = getBitsPerSample(0);
53 final int colorMapScale = 1 << bitsPerPixel;
54 final int colorMapScaleX2;
55 try {
56 colorMapScaleX2 = Math.multiplyExact(2, colorMapScale);
57 } catch (final ArithmeticException e) {
58 throw new ImagingFormatException("bitsPerPixel is too large or colorMap is too small", e);
59 }
60 // Validate colorMap[i], colorMap[i + colorMapScale], and colorMap[i + colorMapScaleX2] where max(i) is
61 // colorMapScale - 1.
62 final int maxI;
63 try {
64 maxI = Math.addExact(colorMapScaleX2, colorMapScale - 1);
65 } catch (final ArithmeticException e) {
66 throw new ImagingFormatException("bitsPerPixel is too large or colorMap is too small", e);
67 }
68 if (maxI >= colorMap.length) {
69 throw new ImagingFormatException("bitsPerPixel %,d (maxI = %,d) is too large or colorMap is too small %,d", bitsPerPixel, maxI, colorMap.length);
70 }
71 indexColorMap = Allocator.intArray(colorMapScale);
72 Arrays.setAll(indexColorMap, i -> {
73 final int red = colorMap[i] >> 8 & 0xff;
74 final int green = colorMap[i + colorMapScale] >> 8 & 0xff;
75 final int blue = colorMap[i + colorMapScaleX2] >> 8 & 0xff;
76 return 0xff000000 | red << 16 | green << 8 | blue;
77 });
78
79 // Fix for IMAGING-247 5/17/2020
80 // This interpreter is used with TIFF_COMPRESSION_PACKBITS (32773).
81 // which unpacks to 8 bits per sample. But if the bits-per-pixel
82 // is less than 8 bits, some authoring tools do not zero-out the
83 // unused bits. This results in cases where the decoded by index
84 // exceeds the size of the palette. So we set up a mask to protect
85 // the code from an array bounds exception.
86 int temp = 0;
87 for (int i = 0; i < bitsPerPixel; i++) {
88 temp = temp << 1 | 1;
89 }
90 bitsPerPixelMask = temp;
91
92 }
93
94 @Override
95 public void interpretPixel(final ImageBuilder imageBuilder, final int[] samples, final int x, final int y) throws ImagingException, IOException {
96 imageBuilder.setRgb(x, y, indexColorMap[samples[0] & bitsPerPixelMask]);
97 }
98 }