Make Exported just be SquirrelJMEVendorApi.
[SquirrelJME.git] / modules / midp-lcdui / src / main / java / cc / squirreljme / runtime / lcdui / image / XPMReader.java
blob2af863bc245a5fb2fcfad9f0ceb3ea4b4dfcd1bd
1 // -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
2 // ---------------------------------------------------------------------------
3 // SquirrelJME
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;
19 /**
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.
29 * @since 2016/05/08
31 public class XPMReader
33 /** Source stream. */
34 protected final InputStream in;
36 /**
37 * Initializes the XPM image reader.
39 * @param __is The input stream.
40 * @since 2016/05/08
42 public XPMReader(InputStream __is)
43 throws NullPointerException
45 if (__is == null)
46 throw new NullPointerException("NARG");
48 this.in = __is;
51 /**
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.
56 * @since 2017/02/10
58 public Image parse()
59 throws IOException
61 InputStream in = this.in;
63 // Create character stripper
64 __CharStripper__ cs = new __CharStripper__(new InputStreamReader(in,
65 "utf-8"));
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);
75 int hotx = header[4];
76 int hoty = header[5];
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);
84 // Target array
85 int area = width * height;
86 int[] data = new int[area];
88 // Read pixels
89 this.__readPixels(cs, width, height, data, pxchars, codes, palette);
91 // Create image
92 return Image.createRGBImage(data, width, height, alpha);
95 /**
96 * Decodes a color key value.
98 * @param __cs The input key value characters.
99 * @return The decoded color value.
100 * @since 2016/05/22
102 private int __decodeColor(CharSequence __cs)
104 // Is the nothing color
105 if ("none".equalsIgnoreCase(__cs.toString()))
106 return 0x00_000000;
108 // Too short?
109 int n = __cs.length();
110 if (n <= 0)
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));
122 // #rgb
123 if (n == 4)
124 return 0xFF_000000 |
125 (dig[0] << 20) |
126 (dig[0] << 16) |
127 (dig[1] << 12) |
128 (dig[1] << 8) |
129 (dig[2] << 4) |
130 (dig[2]);
132 // #argb
133 else if (n == 5)
134 return (dig[0] << 28) |
135 (dig[0] << 24) |
136 (dig[1] << 20) |
137 (dig[1] << 16) |
138 (dig[2] << 12) |
139 (dig[2] << 8) |
140 (dig[3] << 4) |
141 (dig[3]);
143 // #rrggbb
144 else if (n == 7)
145 return 0xFF_000000 |
146 (dig[0] << 20) |
147 (dig[1] << 16) |
148 (dig[2] << 12) |
149 (dig[3] << 8) |
150 (dig[4] << 4) |
151 (dig[5]);
153 // #aarrggbb
154 else if (n == 9)
155 return (dig[0] << 28) |
156 (dig[1] << 24) |
157 (dig[2] << 20) |
158 (dig[3] << 16) |
159 (dig[4] << 12) |
160 (dig[5] << 8) |
161 (dig[6] << 4) |
162 (dig[7]);
164 // Unknown
165 else
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.
176 * @since 2016/05/22
178 private int __locateCode(int __c, int[] __codes, int[] __pal)
180 int at = Arrays.binarySearch(__codes, __c);
181 if (at >= 0)
182 return __pal[at];
183 return 0;
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.
196 * @since 2016/05/22
198 private boolean __readColorTable(Reader __cs, int[] __codes,
199 int[] __palette, int __numcolors, int __pxchars)
200 throws IOException
202 // Had alpha?
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
210 sb.setLength(0);
211 this.__readLine(__cs, sb);
213 // Ignore really short lines
214 int n = sb.length();
215 if (n < __pxchars)
216 continue;
218 // Set code to the given sequence
219 int cx = 0;
220 if (__pxchars >= 1)
221 cx |= (int)sb.charAt(0);
222 if (__pxchars >= 2)
223 cx |= ((int)sb.charAt(1)) << 16;
225 // Find the last color key value
226 int s, e = n - 1;
227 while (e >= __pxchars && sb.charAt(e) <= ' ')
228 e--;
230 // Find the start of the color key
231 s = e -1;
232 while (s >= __pxchars && sb.charAt(s) > ' ')
233 s--;
235 // Decode color, detect if transparency is used
236 int col;
239 col = this.__decodeColor(sb.subSequence(s + 1, e + 1));
241 catch (NotAnXPMColorException ignored)
243 continue;
246 // Is this an alpha pixel?
247 if ((col & 0xFF_000000) != 0xFF_000000)
248 hasalpha = true;
250 // Find the position to place the code at
251 int at = Arrays.binarySearch(__codes, 0, i, cx);
252 if (at < 0)
253 at = -(at + 1);
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];
262 // Set the value
263 __codes[at] = cx;
264 __palette[at] = col;
267 // Return the alpha channel status
268 return hasalpha;
272 * Reads the XPM image heder.
274 * @param __r The source characters.
275 * @return The header values.
276 * @throws IOException On read errors.
277 * @since 2016/05/22
279 private int[] __readHeader(Reader __r)
280 throws IOException
282 // Read XPM header
283 int[] header = new int[7];
284 for (int i = 0;; i++)
285 if (this.__readInt(__r, header, Math.min(6, i)))
286 break;
288 // Return it
289 return header;
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.
300 * @since 2016/05/22
302 private boolean __readInt(Reader __r, int[] __v, int __o)
303 throws IOException
305 // Setup
306 int val = 0;
307 boolean neg = false;
309 // Read character
310 for (boolean first = true, startwhite = true;; first = false)
312 // Read
313 int c = __r.read();
315 // Ignore starting whitespace
316 if (c == ' ' || c == '\t' || c == '\r')
317 if (startwhite)
318 continue;
320 // No more whitespace to ignore
321 startwhite = false;
323 // EOF or EOL?
324 if (c < 0)
326 __v[__o] = (neg ? -val : val);
327 return true;
330 // Negative?
331 if (first && c == '-')
333 neg = true;
334 continue;
337 // As a digit
338 int dig = Character.digit((char)c, 10);
340 // If not a digit, stop
341 if (dig < 0)
343 __v[__o] = (neg ? -val : val);
344 return false;
347 // Shift up and add
348 val *= 10;
349 val += dig;
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.
359 * @since 2015/06/22
361 private void __readLine(Reader __r, StringBuilder __sb)
362 throws IOException
364 // Read until the end
365 for (;;)
367 // Read character
368 int c = __r.read();
370 // End of stream or line?
371 if (c < 0)
372 return;
374 // Append
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.
390 * @since 2016/05/22
392 private void __readPixels(Reader __cs, int __width, int __height,
393 int[] __data, int __pxchars, int[] __codes, int[] __palette)
394 throws IOException
396 // Read the XPM image data for each rows
397 int lastcode = -1;
398 int lastpall = -1;
399 __outer:
400 for (int y = 0; y < __height; y++)
401 for (int x = 0, z = (y * __width);; x++)
403 // Read color code
404 int code = 0;
405 for (int i = 0; i < __pxchars; i++)
407 // Read
408 int c = __cs.read();
410 // Next row?
411 if (c == __CharStripper__.END_OF_LINE)
412 continue __outer;
414 // EOF?
415 else if (c < 0)
416 break __outer;
418 // First read?
419 if (i == 0)
420 code = c;
422 // Second read?
423 else if (i == 1)
424 code |= c << 16;
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
434 else
436 lastpall = this.__locateCode((lastcode = code), __codes,
437 __palette);
438 __data[z++] = lastpall;