1 module dtiv.lib; 2 3 enum 4 { 5 FLAG_FG = 1, 6 FLAG_BG = 2, 7 FLAG_MODE_256 = 4, 8 FLAG_NOT_USE_SKEW = 8, 9 FLAG_NOOPT = 16, 10 FLAG_NOT_USE_BRAILLE = 32 11 } 12 13 /// Get width in chars 14 int calcWidth(T)(in T img) pure 15 { 16 return img.w / 4; 17 } 18 19 /// Get height in chars 20 int calcHeight(T)(in T img) pure 21 { 22 return img.h / 8; 23 } 24 25 struct Pixel 26 { 27 union 28 { 29 struct 30 { 31 ubyte r; 32 ubyte g; 33 ubyte b; 34 }; 35 36 ubyte[3] arr; 37 } 38 39 alias arr this; 40 } 41 42 CharData getChar(Pixel delegate(int x, int y) getPixel, int flags, int x, int y) 43 { 44 return flags & FLAG_NOOPT 45 ? getAverageColor(getPixel, x, y, cast(ushort) 0x2584, cast(uint) 0x0000ffff) 46 : getCharData(getPixel, flags, x, y); 47 } 48 49 struct CharData 50 { 51 Color fgColor; 52 Color bgColor; 53 wchar codePoint; 54 } 55 56 struct Color 57 { 58 union 59 { 60 struct 61 { 62 int r; 63 int g; 64 int b; 65 }; 66 67 int[3] arr; 68 } 69 70 alias arr this; 71 72 Color opOpAssign(string op, T)(in T c) 73 if(op == "+") 74 { 75 r += c.r; 76 g += c.g; 77 b += c.b; 78 79 return this; 80 } 81 82 Color opOpAssign(string op)(int f) 83 if(op == "/") 84 { 85 r /= f; 86 g /= f; 87 b /= f; 88 89 return this; 90 } 91 92 private static ubyte clamp(int e) 93 { 94 return cast(ubyte)( e < 0 ? 0 : (e > 255 ? 255 : e) ); 95 } 96 97 private void clamp2byte() 98 { 99 foreach(ref e; arr) 100 { 101 e = clamp(e); 102 } 103 104 assert(r == toPixel.r); 105 assert(g == toPixel.g); 106 assert(b == toPixel.b); 107 } 108 109 Pixel toPixel() const 110 { 111 Pixel ret; 112 113 foreach(ubyte i, ref e; ret) 114 { 115 e = cast(ubyte) arr[i]; 116 } 117 118 return ret; 119 } 120 } 121 122 private: 123 124 /// Return a CharData struct with the given code point and corresponding averag fg and bg colors. 125 CharData getAverageColor(Pixel delegate(int x, int y) getPixel, int x0, int y0, wchar codepoint, uint pattern) 126 { 127 CharData ret; 128 ret.codePoint = codepoint; 129 uint mask = 0x8000_0000; // Most significant bit 130 uint fg_count; 131 uint bg_count; 132 133 for (ubyte y = 0; y < 8; y++) 134 { 135 for (ubyte x = 0; x < 4; x++) 136 { 137 if (pattern & mask) 138 { 139 ret.fgColor += getPixel(x0 + x, y0 + y); 140 fg_count++; 141 } 142 else 143 { 144 ret.bgColor += getPixel(x0 + x, y0 + y); 145 bg_count++; 146 } 147 148 mask = mask >>> 1; 149 } 150 } 151 152 // Calculate the average color value for each bucket 153 if (bg_count != 0) 154 { 155 ret.bgColor /= bg_count; 156 ret.bgColor.clamp2byte(); // fits value into 0-255 157 } 158 159 if (fg_count != 0) 160 { 161 ret.fgColor /= fg_count; 162 ret.fgColor.clamp2byte(); 163 } 164 165 return ret; 166 } 167 168 /// Find the best character and colors for a 4x8 part of the image at the given position 169 CharData getCharData(Pixel delegate(int x, int y) getPixel, int flags, int x0, int y0) 170 { 171 Color min = {r: 255, g: 255, b: 255}; 172 Color max; 173 174 // Determine the minimum and maximum value for each color channel 175 for (ubyte y = 0; y < 8; y++) 176 for (ubyte x = 0; x < 4; x++) 177 for (ubyte i = 0; i < 3; i++) 178 { 179 static import cmp = std.algorithm.comparison; 180 181 Pixel d = getPixel(x0 + x, y0 + y); 182 min[i] = cmp.min(min[i], d[i]); 183 max[i] = cmp.max(max[i], d[i]); 184 } 185 186 // Determine the color channel with the greatest range. 187 int splitIndex = 0; 188 int bestSplit = 0; 189 for (ubyte i = 0; i < 3; i++) 190 { 191 auto diff = max[i] - min[i]; 192 193 if (diff > bestSplit) 194 { 195 bestSplit = diff; 196 splitIndex = i; 197 } 198 } 199 200 // We just split at the middle of the interval instead of computing the median. 201 int splitValue = min[splitIndex] + bestSplit / 2; 202 203 // Compute a bitmap using the given split and sum the color values for both buckets. 204 uint bits = 0; 205 206 for (ubyte y = 0; y < 8; y++) 207 { 208 for (ubyte x = 0; x < 4; x++) 209 { 210 bits = bits << 1; 211 212 if (getPixel(x0 + x, y0 + y)[splitIndex] > splitValue) 213 bits |= 1; 214 } 215 } 216 217 // Find the best bitmap (box pattern) match by counting the bits 218 // that don't match, including the inverted bitmaps. 219 import dtiv.bitmaps; 220 221 immutable Character[]* currPatterns = 222 (flags & FLAG_NOT_USE_SKEW) ? &boxPatterns : &allBoxPatterns; 223 224 uint best_diff = 8; 225 Character bestChr = {pattern: 0x0000ffff, codePoint: 0x2584}; 226 227 foreach(ref chr; *currPatterns) 228 { 229 uint pattern = chr.pattern; 230 231 for (ubyte j = 0; j < 2; j++) // twice for checking inverted pattern too 232 { 233 import core.bitop; 234 235 int diff = popcnt(pattern ^ bits); 236 237 if (diff < best_diff) 238 { 239 best_diff = diff; 240 241 bestChr.codePoint = chr.codePoint; 242 bestChr.pattern = chr.pattern; 243 } 244 245 pattern = ~pattern; 246 } 247 } 248 249 // Braile patterns check 250 if(!(flags & FLAG_NOT_USE_BRAILLE)) 251 { 252 import dtiv.braille; 253 254 BraillePatternAccum acc; 255 acc.addPattern(bits); 256 uint pattern = acc.pattern; 257 258 for (ubyte j = 0; j < 2; j++) // twice for checking inverted pattern too 259 { 260 import core.bitop; 261 262 int diff = popcnt(pattern ^ bits); 263 264 if (diff < best_diff) 265 { 266 best_diff = diff; 267 268 bestChr.codePoint = acc.codePoint; 269 bestChr.pattern = pattern; 270 } 271 272 pattern = ~pattern; 273 } 274 } 275 276 return getAverageColor(getPixel, x0, y0, bestChr.codePoint, bestChr.pattern); 277 }