1 // -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
2 // ---------------------------------------------------------------------------
4 // Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
5 // ---------------------------------------------------------------------------
6 // SquirrelJME is under the GNU General Public License v3+, or later.
7 // See license.mkd for licensing and copyright information.
8 // ---------------------------------------------------------------------------
10 package cc
.squirreljme
.runtime
.lcdui
.image
;
12 import java
.io
.IOException
;
13 import java
.io
.InputStream
;
14 import java
.io
.InputStreamReader
;
15 import java
.io
.Reader
;
16 import java
.util
.Arrays
;
17 import javax
.microedition
.lcdui
.Image
;
20 * This class is able to read XPM images.
22 * If the XPM is invalid then the read image data will not be correct.
24 * There are also limitations to the reader, only the last color key will be
25 * used and it will be treated as a RGB hexadecimal color. Also the pixels
26 * per character has a limit of 2 characters, any pixels with characters
27 * codes beyond 2 character will only use the first 2.
31 public class XPMReader
34 protected final InputStream in
;
37 * Initializes the XPM image reader.
39 * @param __is The input stream.
42 public XPMReader(InputStream __is
)
43 throws NullPointerException
46 throw new NullPointerException("NARG");
52 * Reads the XPM image data from the specified input stream.
54 * @return The read image data.
55 * @throws IOException If the XPM is not valid.
61 InputStream in
= this.in
;
63 // Create character stripper
64 __CharStripper__ cs
= new __CharStripper__(new InputStreamReader(in
,
67 // Read the XPM header
68 int[] header
= this.__readHeader(cs
);
70 // Get dimensional data
71 int width
= Math
.max(header
[0], 1);
72 int height
= Math
.max(header
[1], 1);
73 int numcolors
= Math
.max(header
[2], 1);
74 int pxchars
= Math
.max(header
[3], 1);
78 // Read the color table
79 int[] codes
= new int[numcolors
];
80 int[] palette
= new int[numcolors
];
81 boolean alpha
= this.__readColorTable(
82 cs
, codes
, palette
, numcolors
, pxchars
);
85 int area
= width
* height
;
86 int[] data
= new int[area
];
89 this.__readPixels(cs
, width
, height
, data
, pxchars
, codes
, palette
);
92 return Image
.createRGBImage(data
, width
, height
, alpha
);
96 * Decodes a color key value.
98 * @param __cs The input key value characters.
99 * @return The decoded color value.
102 private int __decodeColor(CharSequence __cs
)
104 // Is the nothing color
105 if ("none".equalsIgnoreCase(__cs
.toString()))
109 int n
= __cs
.length();
111 throw new NotAnXPMColorException(__cs
.toString());
113 // Must start with '#'
114 if (__cs
.charAt(0) != '#')
115 throw new NotAnXPMColorException(__cs
.toString());
117 // Decode the first 8 digits
118 int[] dig
= new int[8];
119 for (int i
= 0, j
= 1; i
< 8 && j
< n
; i
++, j
++)
120 dig
[i
] = Math
.max(0, Character
.digit(__cs
.charAt(j
), 16));
134 return (dig
[0] << 28) |
155 return (dig
[0] << 28) |
166 throw new NotAnXPMColorException(__cs
.toString());
170 * Locates a color for a given color.
172 * @param __c The code to find the color for.
173 * @param __codes The array of codes.
174 * @param __pal The color palette.
175 * @return The color code used.
178 private int __locateCode(int __c
, int[] __codes
, int[] __pal
)
180 int at
= Arrays
.binarySearch(__codes
, __c
);
187 * Reads the color table of the XPM.
189 * @param __cs The source characters.
190 * @param __codes The output color codes.
191 * @param __palette The output color palette.
192 * @param __numcolors The number of colors used.
193 * @param __pxchars The number of characters per pixel.
194 * @return If an alpha channel was used.
195 * @throws IOException On read errors.
198 private boolean __readColorTable(Reader __cs
, int[] __codes
,
199 int[] __palette
, int __numcolors
, int __pxchars
)
203 boolean hasalpha
= false;
205 // Decode the color palette
206 StringBuilder sb
= new StringBuilder();
207 for (int i
= 0; i
< __numcolors
; i
++)
209 // Read new input string
211 this.__readLine(__cs
, sb
);
213 // Ignore really short lines
218 // Set code to the given sequence
221 cx
|= (int)sb
.charAt(0);
223 cx
|= ((int)sb
.charAt(1)) << 16;
225 // Find the last color key value
227 while (e
>= __pxchars
&& sb
.charAt(e
) <= ' ')
230 // Find the start of the color key
232 while (s
>= __pxchars
&& sb
.charAt(s
) > ' ')
235 // Decode color, detect if transparency is used
239 col
= this.__decodeColor(sb
.subSequence(s
+ 1, e
+ 1));
241 catch (NotAnXPMColorException ignored
)
246 // Is this an alpha pixel?
247 if ((col
& 0xFF_
000000) != 0xFF_
000000)
250 // Find the position to place the code at
251 int at
= Arrays
.binarySearch(__codes
, 0, i
, cx
);
255 // Move all values up
256 for (int j
= i
; j
> at
; j
--)
258 __codes
[j
] = __codes
[j
- 1];
259 __palette
[j
] = __palette
[j
- 1];
267 // Return the alpha channel status
272 * Reads the XPM image heder.
274 * @param __r The source characters.
275 * @return The header values.
276 * @throws IOException On read errors.
279 private int[] __readHeader(Reader __r
)
283 int[] header
= new int[7];
284 for (int i
= 0;; i
++)
285 if (this.__readInt(__r
, header
, Math
.min(6, i
)))
293 * Reads a single integer value from the input.
295 * @param __r The stream to read an integer from.
296 * @param __v The read value.
297 * @param __o The offset in the array index.
298 * @return {@code true} if the line or stream has ended.
299 * @throws IOException On read errors.
302 private boolean __readInt(Reader __r
, int[] __v
, int __o
)
310 for (boolean first
= true, startwhite
= true;; first
= false)
315 // Ignore starting whitespace
316 if (c
== ' ' || c
== '\t' || c
== '\r')
320 // No more whitespace to ignore
326 __v
[__o
] = (neg ?
-val
: val
);
331 if (first
&& c
== '-')
338 int dig
= Character
.digit((char)c
, 10);
340 // If not a digit, stop
343 __v
[__o
] = (neg ?
-val
: val
);
354 * Reads a single line into the given string builder.
356 * @param __r The stream to source characters from.
357 * @param __sb The buffer to store the temporary string data.
358 * @throws IOException On read errors.
361 private void __readLine(Reader __r
, StringBuilder __sb
)
364 // Read until the end
370 // End of stream or line?
375 __sb
.append((char)c
);
380 * Reads the pixels from the XPM image.
382 * @param __cs The character source.
383 * @param __width The image width.
384 * @param __height The image height.
385 * @param __data The output data.
386 * @param __pxchars The characters per pixel.
387 * @param __codes The character codes.
388 * @param __palette The color palette.
389 * @throws IOException On read errors.
392 private void __readPixels(Reader __cs
, int __width
, int __height
,
393 int[] __data
, int __pxchars
, int[] __codes
, int[] __palette
)
396 // Read the XPM image data for each rows
400 for (int y
= 0; y
< __height
; y
++)
401 for (int x
= 0, z
= (y
* __width
);; x
++)
405 for (int i
= 0; i
< __pxchars
; i
++)
411 if (c
== __CharStripper__
.END_OF_LINE
)
427 // Used this color just before? In solidly linear areas, this
428 // reduces the need for constant binary searches and increases
429 // the parsing speed slightly.
430 if (code
== lastcode
)
431 __data
[z
++] = lastpall
;
433 // Find the code used
436 lastpall
= this.__locateCode((lastcode
= code
), __codes
,
438 __data
[z
++] = lastpall
;