1 module dlangui.graphics.xpm.reader;
2 
3 /**
4  * Reading .xpm files.
5  *
6  * Copyright: Roman Chistokhodov, 2015
7  * License:   Boost License 1.0
8  * Authors:   Roman Chistokhodov, freeslave93@gmail.com
9  *
10  */
11 
12 import dlangui.graphics.xpm.xpmcolors;
13 
14 import dlangui.graphics.colors;
15 import dlangui.graphics.drawbuf;
16 
17 import std.algorithm : startsWith, splitter, find, equal;
18 import std.array;
19 import std.string;
20 import std.range;
21 import std.exception;
22 import std.format : formattedRead;
23 
24 private const(char)[] extractXPMString(const(char)[] str) {
25     auto firstIndex = str.indexOf('"');
26     if (firstIndex != -1) {
27         auto secondIndex = str.indexOf('"', firstIndex+1);
28         if (secondIndex != -1) {
29             return str[firstIndex+1..secondIndex];
30         }
31     }
32     return null;
33 }
34 
35 private uint parseRGB(in char[] rgbStr)
36 {
37     static ubyte parsePrimaryColor(const(char)[] subStr) {
38         ubyte c;
39         enforce(formattedRead(subStr, "%x", &c) == 1, "Could not parse RGB value");
40         return c;
41     }
42     enforce(rgbStr.length == 6, rgbStr ~ " : RGB string must have length of 6");
43     ubyte red = parsePrimaryColor(rgbStr[0..2]);
44     ubyte green = parsePrimaryColor(rgbStr[2..4]);
45     ubyte blue = parsePrimaryColor(rgbStr[4..6]);
46 
47     return makeRGBA(red, green, blue, 0);
48 }
49 
50 //Unique hashes for non-empty strings with length <= 8
51 private ulong xpmHash(in char[] str) {
52     ulong hash = 0;
53     foreach(c; str.representation) {
54         hash <<= 8;
55         hash += c;
56     }
57     return hash;
58 }
59 
60 ColorDrawBuf parseXPM(const(ubyte)[] data)
61 {
62     auto buf = cast(const(char)[])(data);
63     auto lines = buf.splitter('\n');
64 
65     enforce(!lines.empty, "No data");
66 
67     //Read magic
68     auto firstLine = lines.front;
69     enforce(firstLine.startsWith("/* XPM"), "No magic");
70     lines.popFront();
71 
72     //Read values
73     int w, h, ncols, cpp;
74     while(!lines.empty) {
75         auto str = extractXPMString(lines.front);
76 
77         if (str.length) {
78             enforce(formattedRead(str, " %d %d %d %d", &w, &h, &ncols, &cpp) == 4, "Could not read values");
79             enforce(cpp > 0, "Bad character per pixel value");
80             enforce(cpp <= 8, "Character per pixel value is too big");
81             lines.popFront();
82             break;
83         }
84         lines.popFront();
85     }
86 
87     //Read color map
88     size_t colorsRead = 0;
89     auto sortedColors = assumeSorted(predefinedColors);
90     uint[ulong] colorMap;
91 
92     while(!lines.empty && colorsRead != ncols) {
93         auto str = extractXPMString(lines.front);
94         if (str.length) {
95             auto key = str[0..cpp];
96 
97 
98             auto tokens = str[cpp..$].strip.splitter(' ');
99             auto prefixRange = tokens.find("c");
100 
101             enforce(!prefixRange.empty, "Could not find color visual prefix");
102 
103             auto colorRange = prefixRange.drop(1);
104             enforce(!colorRange.empty, "Could not get color value for " ~ key);
105 
106             auto colorStr = colorRange.front;
107             auto hash = xpmHash(key);
108 
109             enforce(hash !in colorMap, key ~ " : same key is defined twice");
110 
111             if (colorStr[0] == '#') {
112                 colorMap[hash] = parseRGB(colorStr[1..$]);
113             } else if (colorStr == "None") {
114                 colorMap[hash] = makeRGBA(0,0,0,255);
115             } else {
116                 auto t = sortedColors.equalRange(colorStr);
117                 enforce(!t.empty, "Could not find color named " ~ colorStr);
118                 auto c = t.front;
119 
120                 colorMap[hash] = makeRGBA(c.red, c.green, c.blue, 0);
121             }
122 
123             colorsRead++;
124         }
125         lines.popFront();
126     }
127 
128     enforce(colorsRead == ncols, "Could not load color table");
129 
130     //Read pixels
131     ColorDrawBuf colorBuf = new ColorDrawBuf(w, h);
132 
133     for (int y = 0; y<h && !lines.empty; y++) {
134         auto str = extractXPMString(lines.front);
135         uint* dstLine = colorBuf.scanLine(y);
136         if (str.length) {
137             enforce(str.length >= w*cpp, "Invalid pixel line");
138             foreach(int x; 0 .. w) {
139                 auto pixelStr = str[x*cpp..(x+1)*cpp];
140                 auto colorPtr = xpmHash(pixelStr) in colorMap;
141                 enforce(colorPtr, "Unknown pixel : '" ~ str ~ "'");
142                 dstLine[x] = *colorPtr;
143             }
144         }
145         lines.popFront();
146     }
147 
148     return colorBuf;
149 }