1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.commons.imaging.formats.pcx;
17
18 import java.awt.image.BufferedImage;
19 import java.io.IOException;
20 import java.io.OutputStream;
21 import java.util.Arrays;
22
23 import org.apache.commons.imaging.PixelDensity;
24 import org.apache.commons.imaging.common.AbstractBinaryOutputStream;
25 import org.apache.commons.imaging.common.Allocator;
26 import org.apache.commons.imaging.palette.PaletteFactory;
27 import org.apache.commons.imaging.palette.SimplePalette;
28
29 final class PcxWriter {
30 private final int encoding;
31 private final int bitDepthWanted;
32 private final int planesWanted;
33 private final PixelDensity pixelDensity;
34 private final RleWriter rleWriter;
35
36 PcxWriter(PcxImagingParameters params) {
37
38
39 if (params == null) {
40 params = new PcxImagingParameters();
41 }
42 encoding = params.getCompression() == PcxConstants.PCX_COMPRESSION_UNCOMPRESSED ? PcxImageParser.PcxHeader.ENCODING_UNCOMPRESSED
43 : PcxImageParser.PcxHeader.ENCODING_RLE;
44 rleWriter = new RleWriter(encoding != PcxImageParser.PcxHeader.ENCODING_UNCOMPRESSED);
45 bitDepthWanted = params.getBitDepth();
46 planesWanted = params.getPlanes();
47 final PixelDensity pixelDensityParam = params.getPixelDensity();
48
49 pixelDensity = pixelDensityParam != null ? pixelDensityParam : PixelDensity.createFromPixelsPerInch(72, 72);
50 }
51
52 public void writeImage(final BufferedImage src, final OutputStream os) throws IOException {
53 final PaletteFactory paletteFactory = new PaletteFactory();
54 final SimplePalette palette = paletteFactory.makeExactRgbPaletteSimple(src, 256);
55 @SuppressWarnings("resource")
56 final AbstractBinaryOutputStream bos = AbstractBinaryOutputStream.littleEndian(os);
57 final int bitDepth;
58 final int planes;
59 if (palette == null || bitDepthWanted == 24 || bitDepthWanted == 32) {
60 if (bitDepthWanted == 32) {
61 bitDepth = 32;
62 planes = 1;
63 } else {
64 bitDepth = 8;
65 planes = 3;
66 }
67 } else if (palette.length() > 16 || bitDepthWanted == 8) {
68 bitDepth = 8;
69 planes = 1;
70 } else if (palette.length() > 8 || bitDepthWanted == 4) {
71 if (planesWanted == 1) {
72 bitDepth = 4;
73 planes = 1;
74 } else {
75 bitDepth = 1;
76 planes = 4;
77 }
78 } else if (palette.length() > 4 || bitDepthWanted == 3) {
79 bitDepth = 1;
80 planes = 3;
81 } else if (palette.length() > 2 || bitDepthWanted == 2) {
82 if (planesWanted == 2) {
83 bitDepth = 1;
84 planes = 2;
85 } else {
86 bitDepth = 2;
87 planes = 1;
88 }
89 } else {
90 boolean onlyBlackAndWhite = true;
91 if (palette.length() >= 1) {
92 final int rgb = palette.getEntry(0);
93 if (rgb != 0 && rgb != 0xffffff) {
94 onlyBlackAndWhite = false;
95 }
96 }
97 if (palette.length() == 2) {
98 final int rgb = palette.getEntry(1);
99 if (rgb != 0 && rgb != 0xffffff) {
100 onlyBlackAndWhite = false;
101 }
102 }
103 if (onlyBlackAndWhite) {
104 bitDepth = 1;
105 planes = 1;
106 } else {
107 bitDepth = 1;
108 planes = 2;
109 }
110 }
111
112 int bytesPerLine = (bitDepth * src.getWidth() + 7) / 8;
113 if (bytesPerLine % 2 != 0) {
114
115 bytesPerLine++;
116 }
117
118 final byte[] palette16 = new byte[16 * 3];
119
120 final int paletteLen = palette != null ? palette.length() : 0;
121 for (int i = 0; i < 16; i++) {
122 final int rgb;
123 if (i < paletteLen) {
124 rgb = palette.getEntry(i);
125 } else {
126 rgb = 0;
127 }
128 palette16[3 * i + 0] = (byte) (0xff & rgb >> 16);
129 palette16[3 * i + 1] = (byte) (0xff & rgb >> 8);
130 palette16[3 * i + 2] = (byte) (0xff & rgb);
131 }
132
133
134 bos.write(10);
135 bos.write(bitDepth == 1 && planes == 1 ? 3 : 5);
136 bos.write(encoding);
137 bos.write(bitDepth);
138 bos.write2Bytes(0);
139 bos.write2Bytes(0);
140 bos.write2Bytes(src.getWidth() - 1);
141 bos.write2Bytes(src.getHeight() - 1);
142 bos.write2Bytes((short) Math.round(pixelDensity.horizontalDensityInches()));
143 bos.write2Bytes((short) Math.round(pixelDensity.verticalDensityInches()));
144 bos.write(palette16);
145 bos.write(0);
146 bos.write(planes);
147 bos.write2Bytes(bytesPerLine);
148 bos.write2Bytes(1);
149 bos.write2Bytes(0);
150 bos.write2Bytes(0);
151 bos.write(new byte[54]);
152
153 if (bitDepth == 32) {
154 writePixels32(src, bytesPerLine, bos);
155 } else {
156 writePixels(src, bitDepth, planes, bytesPerLine, palette, bos);
157 }
158
159 if (bitDepth == 8 && planes == 1) {
160
161 bos.write(12);
162 for (int i = 0; i < 256; i++) {
163 final int rgb;
164 if (i < palette.length()) {
165 rgb = palette.getEntry(i);
166 } else {
167 rgb = 0;
168 }
169 bos.write(rgb >> 16 & 0xff);
170 bos.write(rgb >> 8 & 0xff);
171 bos.write(rgb & 0xff);
172 }
173 }
174 }
175
176 private void writePixels(final BufferedImage src, final int bitDepth, final int planes, final int bytesPerLine, final SimplePalette palette,
177 final AbstractBinaryOutputStream bos) throws IOException {
178 final byte[] plane0 = Allocator.byteArray(bytesPerLine);
179 final byte[] plane1 = Allocator.byteArray(bytesPerLine);
180 final byte[] plane2 = Allocator.byteArray(bytesPerLine);
181 final byte[] plane3 = Allocator.byteArray(bytesPerLine);
182 final byte[][] allPlanes = { plane0, plane1, plane2, plane3 };
183
184 for (int y = 0; y < src.getHeight(); y++) {
185 for (int i = 0; i < planes; i++) {
186 Arrays.fill(allPlanes[i], (byte) 0);
187 }
188
189 if (bitDepth == 1 && planes == 1) {
190 for (int x = 0; x < src.getWidth(); x++) {
191 final int rgb = 0xffffff & src.getRGB(x, y);
192 final int bit;
193 if (rgb == 0x000000) {
194 bit = 0;
195 } else {
196 bit = 1;
197 }
198 plane0[x >>> 3] |= bit << 7 - (x & 7);
199 }
200 } else if (bitDepth == 1 && planes == 2) {
201 for (int x = 0; x < src.getWidth(); x++) {
202 final int argb = src.getRGB(x, y);
203 final int index = palette.getPaletteIndex(0xffffff & argb);
204 plane0[x >>> 3] |= (index & 1) << 7 - (x & 7);
205 plane1[x >>> 3] |= (index & 2) >> 1 << 7 - (x & 7);
206 }
207 } else if (bitDepth == 1 && planes == 3) {
208 for (int x = 0; x < src.getWidth(); x++) {
209 final int argb = src.getRGB(x, y);
210 final int index = palette.getPaletteIndex(0xffffff & argb);
211 plane0[x >>> 3] |= (index & 1) << 7 - (x & 7);
212 plane1[x >>> 3] |= (index & 2) >> 1 << 7 - (x & 7);
213 plane2[x >>> 3] |= (index & 4) >> 2 << 7 - (x & 7);
214 }
215 } else if (bitDepth == 1 && planes == 4) {
216 for (int x = 0; x < src.getWidth(); x++) {
217 final int argb = src.getRGB(x, y);
218 final int index = palette.getPaletteIndex(0xffffff & argb);
219 plane0[x >>> 3] |= (index & 1) << 7 - (x & 7);
220 plane1[x >>> 3] |= (index & 2) >> 1 << 7 - (x & 7);
221 plane2[x >>> 3] |= (index & 4) >> 2 << 7 - (x & 7);
222 plane3[x >>> 3] |= (index & 8) >> 3 << 7 - (x & 7);
223 }
224 } else if (bitDepth == 2 && planes == 1) {
225 for (int x = 0; x < src.getWidth(); x++) {
226 final int argb = src.getRGB(x, y);
227 final int index = palette.getPaletteIndex(0xffffff & argb);
228 plane0[x >>> 2] |= index << 2 * (3 - (x & 3));
229 }
230 } else if (bitDepth == 4 && planes == 1) {
231 for (int x = 0; x < src.getWidth(); x++) {
232 final int argb = src.getRGB(x, y);
233 final int index = palette.getPaletteIndex(0xffffff & argb);
234 plane0[x >>> 1] |= index << 4 * (1 - (x & 1));
235 }
236 } else if (bitDepth == 8 && planes == 1) {
237 for (int x = 0; x < src.getWidth(); x++) {
238 final int argb = src.getRGB(x, y);
239 final int index = palette.getPaletteIndex(0xffffff & argb);
240 plane0[x] = (byte) index;
241 }
242 } else if (bitDepth == 8 && planes == 3) {
243 for (int x = 0; x < src.getWidth(); x++) {
244 final int argb = src.getRGB(x, y);
245 plane0[x] = (byte) (argb >>> 16);
246 plane1[x] = (byte) (argb >>> 8);
247 plane2[x] = (byte) argb;
248 }
249 }
250
251 for (int i = 0; i < planes; i++) {
252 rleWriter.write(bos, allPlanes[i]);
253 }
254 }
255 rleWriter.flush(bos);
256 }
257
258 private void writePixels32(final BufferedImage src, final int bytesPerLine, final AbstractBinaryOutputStream bos) throws IOException {
259
260 final int[] rgbs = Allocator.intArray(src.getWidth());
261 final byte[] plane = Allocator.byteArray(4 * bytesPerLine);
262 for (int y = 0; y < src.getHeight(); y++) {
263 src.getRGB(0, y, src.getWidth(), 1, rgbs, 0, src.getWidth());
264 for (int x = 0; x < rgbs.length; x++) {
265 plane[4 * x + 0] = (byte) rgbs[x];
266 plane[4 * x + 1] = (byte) (rgbs[x] >> 8);
267 plane[4 * x + 2] = (byte) (rgbs[x] >> 16);
268 plane[4 * x + 3] = 0;
269 }
270 rleWriter.write(bos, plane);
271 }
272 rleWriter.flush(bos);
273 }
274 }