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 }