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 }