Advertisement
Guest User

Inside ClearType in Windows Longhorn By Maxim Shemanarev

a guest
Dec 20th, 2012
468
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 18.96 KB | None | 0 0
  1. Inside ClearType in Windows Longhorn
  2. By Maxim Shemanarev
  3. April 11, 2005
  4. (Inside ClearType in Windows Longhorn : Page 1 of 1 )
  5.  
  6.  
  7.  
  8. Some time ago programmer and author Ian Griffiths wrote in his blog about the main problem with ClearType in
  9. Windows XP. It turns out that while ClearType is great for rendering small glyphs, large ones look a lot
  10. more jagged, especially in cases like the top and bottom of an upper-case "O." In contrast, Ian admires the
  11. new ClearType algorithm in Longhorn because that very O looks smooth.
  12.  
  13. Steve Gibson (the programmer most famous for his "ShieldsUP!" security checking site) has done some
  14. research about how it works in general. After I carefully studied Steve's method I came to the conclusion
  15. that the secret is in anti-aliasing. The ClearType technology is essentially anti-aliasing too (LCD
  16. subpixel), but it works well only for vertical or nearly vertical edges. At the same time, classical gray
  17. scale anti-aliasing does not depend on the edge direction. So, if we combine both gray scale and LCD
  18. subpixel anti-aliasing techniques, we can obtain results very similar to what we see in Longhorn.
  19.  
  20. I'm presenting here source code in C++ for achieving Longhorn-like results in typeface rendering. (For
  21. examples of the results, see http://antigrain.com/stuff/lcd_font.png, and
  22. http://antigrain.com/stuff/lcd_font_zoom.png for a zoomed-in version.) The code is written for
  23. WinAPI and doesn't depend on any other libraries or tools. To obtain gray scale Anti-Aliased glyphs, I
  24. used the WinAPI function GetGlyphOutline(), with 65 levels of gray. That seems enough to try out the idea.
  25. The glyphs are rendered with triple X-axis resolution and then alpha-blended with the canvas
  26. considering color triplets in LCD displays.
  27.  
  28. The bulk of this article consists of two listings. Listing One gives all rendering classes and functions,
  29. while Listing Two contains an example of the WM_PAINT event processing.
  30.  
  31. A few notes:
  32. This doesn't work with CRT displays (including Trinitron ones).
  33. To keep the code as simple as possible, I assume the R-G-B order in the color triplets (I've never seen B-G-R
  34. one). But in memory the order of bytes is namely B-G-R, so that I have to swap Red and Blue bytes before
  35. displaying the buffer. It's done in function swap_rb(). If you want to work directly with B-G-R, you will
  36. have to modify blend_lcd_span(), but it might not be that trivial.
  37. The color can be translucent; that is, alpha blending is supported.
  38. There is no clipping. This means that if you try to render something outside the buffer, the program will
  39. crash. Clipping is a separate, trivial task and not included into this demonstration.
  40. This code iss relatively slow, mostly because of the WinAPI function GetGlyphOutline(). Another
  41. time-consuming operation is the correction of energy distribution in the function
  42. prepare_lcd_glyph(). If you create a cache of glyphs after prepare_lcd_glyph(), the speed will be
  43. comparable with native hardware-accelerated font rendering.
  44. You can download the full project for Microsoft Visuial C++ 6.0 here.
  45. It would be pretty interesting to use this idea for general vector graphic rendering. I have my own 2D
  46. rendering progect called Anti-Grain Geometry and I will try to incorporate this technique there.
  47. Listing One
  48.  
  49. //---------------------------------
  50. // A simple helper class to create font, select object
  51. // and properly destroy it.
  52. class lcd_font
  53. {
  54. public:
  55. ~lcd_font()
  56. {
  57. ::SelectObject(m_hdc, m_old_font);
  58. ::DeleteObject(m_font);
  59. }
  60.  
  61.  
  62. lcd_font(HDC hdc, const char* typeface, int height, bool bold=false,
  63. bool italic=false) :
  64. m_hdc(hdc),
  65. m_font(::CreateFont(height, // height of font
  66. 0, // average character width
  67. 0, // angle of escapement
  68. 0, // base-line orientation
  69. angle
  70. bold ? 700 : 400, // font weight
  71. italic, // italic attribute option
  72. FALSE, // underline attribute
  73. option
  74. FALSE, // strikeout attribute
  75. option
  76. ANSI_CHARSET, // character set
  77. identifier
  78. OUT_DEFAULT_PRECIS, // output precision
  79. CLIP_DEFAULT_PRECIS, // clipping precision
  80. ANTIALIASED_QUALITY, // output quality
  81. FF_DONTCARE, // pitch and family
  82. typeface)), // typeface name
  83. m_old_font(::SelectObject(m_hdc, m_font))
  84. {
  85. }
  86.  
  87. private:
  88. HDC m_hdc;
  89. HFONT m_font;
  90. HGDIOBJ m_old_font;
  91. };
  92.  
  93.  
  94. // Possible formats for GetGlyphOutline() and corresponding
  95. // numbers of levels of gray.
  96. //---------------------------------
  97. struct ggo_gray2 { enum { num_levels = 5, format = GGO_GRAY2_BITMAP }; };
  98. struct ggo_gray4 { enum { num_levels = 17, format = GGO_GRAY4_BITMAP }; };
  99. struct ggo_gray8 { enum { num_levels = 65, format = GGO_GRAY8_BITMAP }; };
  100.  
  101.  
  102. // Sub-pixel energy distribution lookup table.
  103. // See description by Steve Gibson: http://grc.com/cttech.htm
  104.  
  105. // The class automatically normalizes the coefficients
  106. // in such a way that primary + 2*secondary + 3*tertiary = 1.0
  107. // Also, the input values are in range of 0...NumLevels, output ones
  108. // are 0...255
  109. //---------------------------------
  110. template<class GgoFormat> class lcd_distribution_lut
  111. {
  112. public:
  113. lcd_distribution_lut(double prim, double second, double tert)
  114. {
  115. double norm = (255.0 / (GgoFormat::num_levels - 1)) / (prim +
  116. second*2 + tert*2);
  117. prim *= norm;
  118. second *= norm;
  119. tert *= norm;
  120. for(unsigned i = 0; i < GgoFormat::num_levels; i++)
  121. {
  122. m_primary[i] = (unsigned char)floor(prim * i);
  123. m_secondary[i] = (unsigned char)floor(second * i);
  124. m_tertiary[i] = (unsigned char)floor(tert * i);
  125. }
  126. }
  127.  
  128. unsigned primary(unsigned v) const { return m_primary[v]; }
  129. unsigned secondary(unsigned v) const { return m_secondary[v]; }
  130. unsigned tertiary(unsigned v) const { return m_tertiary[v]; }
  131.  
  132. static unsigned ggo_format()
  133. {
  134. return GgoFormat::format;
  135. }
  136.  
  137. private:
  138. unsigned char m_primary[GgoFormat::num_levels];
  139. unsigned char m_secondary[GgoFormat::num_levels];
  140. unsigned char m_tertiary[GgoFormat::num_levels];
  141. };
  142.  
  143.  
  144.  
  145. // This function prepares the alpha-channel information
  146. // for the glyph averaging the values in accordance with
  147. // the method suggested by Steve Gibson. The function
  148. // extends the width by 4 extra pixels, 2 at the beginning
  149. // and 2 at the end. Also, it doesn't align the new width
  150. // to 4 bytes, that is, the output gm.gmBlackBoxX is the
  151. // actual width of the array.
  152. //---------------------------------
  153. template<class LutType>
  154. void prepare_lcd_glyph(const LutType& lut,
  155. const unsigned char* gbuf1,
  156. const GLYPHMETRICS& gm,
  157. unsigned char* gbuf2,
  158. GLYPHMETRICS* gm2)
  159. {
  160. unsigned src_stride = (gm.gmBlackBoxX + 3) / 4 * 4;
  161. unsigned dst_width = src_stride + 4;
  162. memset(gbuf2, 0, dst_width * gm.gmBlackBoxY);
  163.  
  164. for(unsigned y = 0; y < gm.gmBlackBoxY; ++y)
  165. {
  166. const unsigned char* src_ptr = gbuf1 + src_stride * y;
  167. unsigned char* dst_ptr = gbuf2 + dst_width * y;
  168. unsigned x;
  169. for(x = 0; x < gm.gmBlackBoxX; ++x)
  170. {
  171. unsigned v = *src_ptr++;
  172. dst_ptr[0] += lut.tertiary(v);
  173. dst_ptr[1] += lut.secondary(v);
  174. dst_ptr[2] += lut.primary(v);
  175. dst_ptr[3] += lut.secondary(v);
  176. dst_ptr[4] += lut.tertiary(v);
  177. ++dst_ptr;
  178. }
  179. }
  180. gm2->gmBlackBoxX = dst_width;
  181. }
  182.  
  183.  
  184. // Color struct
  185. //---------------------------------
  186. struct rgba
  187. {
  188. rgba() : r(0), g(0), b(0), a(255) {}
  189. rgba(unsigned char r_, unsigned char g_, unsigned char b_, unsigned char
  190. a_=255) :
  191. r(r_), g(g_), b(b_), a(a_) {}
  192. unsigned char r,g,b,a;
  193. };
  194.  
  195.  
  196.  
  197. // Blend one span into the R-G-B 24 bit frame buffer
  198. // For the B-G-R byte order or for 32-bit buffers modify
  199. // this function accordingly. The general idea is 'span'
  200. // contains alpha values for individual color channels in the
  201. // R-G-B order, so, for the B-G-R order you will have to
  202. // choose values from the 'span' array differently
  203. //---------------------------------
  204. void blend_lcd_span(int x,
  205. int y,
  206. const unsigned char* span,
  207. int width,
  208. const rgba& color,
  209. unsigned char* rgb24_buf,
  210. unsigned rgb24_stride)
  211. {
  212. unsigned char* p = rgb24_buf + rgb24_stride * y + x;
  213. unsigned char rgb[3] = { color.r, color.g, color.b };
  214. int i = x % 3;
  215. do
  216. {
  217. int a0 = int(*span++) * color.a;
  218. *p++ = (unsigned char)((((rgb[i++] - *p) * a0) + (*p << 16)) >> 16);
  219. if(i > 2) i = 0;
  220. }
  221. while(--width);
  222. }
  223.  
  224.  
  225.  
  226. // Blend one rectangular glyph
  227. //---------------------------------
  228. void blend_lcd_glyph(const unsigned char* gbuf,
  229. int x,
  230. int y,
  231. const rgba& color,
  232. const GLYPHMETRICS& gm,
  233. unsigned char* rgb24_buf,
  234. unsigned rgb24_stride)
  235. {
  236.  
  237. for(unsigned i = 0; i < gm.gmBlackBoxY; i++)
  238. {
  239. blend_lcd_span(x + gm.gmptGlyphOrigin.x,
  240. y + gm.gmptGlyphOrigin.y - i,
  241. gbuf + gm.gmBlackBoxX * i,
  242. gm.gmBlackBoxX,
  243. color,
  244. rgb24_buf,
  245. rgb24_stride);
  246. }
  247. }
  248.  
  249.  
  250.  
  251. // Draw a text string in the frame buffer
  252. //---------------------------------
  253. template<class LutType, class CharT>
  254. void draw_lcd_text(HDC hdc,
  255. const LutType& lut,
  256. int x,
  257. int y,
  258. const rgba& color,
  259. const CharT* str,
  260. unsigned char* rgb24,
  261. unsigned stride)
  262. {
  263. // Create an affine matrix with 3x horizontal scaling.
  264. // 3x means that we interpret each pixel in the resulting glyph
  265. // in such a way that it corresponds to the sublixel
  266. // (red, green, or blue), but not to the whole pixel.
  267. //----------------------------------
  268. MAT2 scale3h;
  269. memset(&scale3h, 0, sizeof(MAT2));
  270. scale3h.eM11.value = 3;
  271. scale3h.eM22.value = 1;
  272.  
  273. // Allocate buffers for glyphs
  274. // In reality use some smarter strategy to detect
  275. // the size of the buffer
  276. unsigned gbuf_size = 16*1024;
  277. unsigned char* gbuf1 = new unsigned char [gbuf_size];
  278. unsigned char* gbuf2 = new unsigned char [gbuf_size];
  279.  
  280. while(*str)
  281. {
  282. GLYPHMETRICS gm;
  283. int total_size = GetGlyphOutline(hdc,
  284. *str,
  285. lut.ggo_format(),
  286. &gm,
  287. gbuf_size,
  288. (void*)gbuf1,
  289. &scale3h);
  290. if(total_size >= 0)
  291. {
  292. prepare_lcd_glyph(lut, gbuf1, gm, gbuf2, &gm);
  293. blend_lcd_glyph(gbuf2, x, y, color, gm, rgb24, stride);
  294. }
  295. else
  296. {
  297. // GetGlyphOutline() fails when being called for
  298. // GGO_GRAY8_BITMAP and white space (stupid Microsoft).
  299. // It doesn't even initialize the glyph metrics
  300. // structure. So, we have to query the metrics
  301. // separately (basically we need gmCellIncX).
  302. total_size = GetGlyphOutline(hdc,
  303. *str,
  304. GGO_METRICS,
  305. &gm,
  306. gbuf_size,
  307. (void*)gbuf1,
  308. &scale3h);
  309. }
  310.  
  311. x += gm.gmCellIncX;
  312. ++str;
  313. }
  314. delete [] gbuf2;
  315. delete [] gbuf1;
  316. }
  317.  
  318.  
  319.  
  320. // Swap Blue and Red, that is convert RGB->BGR or BGR->RGB
  321. //---------------------------------
  322. void swap_rb(unsigned char* buf, unsigned width, unsigned height, unsigned
  323. stride)
  324. {
  325. unsigned x, y;
  326. for(y = 0; y < height; ++y)
  327. {
  328. unsigned char* p = buf + stride * y;
  329. for(x = 0; x < width; ++x)
  330. {
  331. unsigned char v = p[0];
  332. p[0] = p[2];
  333. p[2] = v;
  334. p += 3;
  335. }
  336. }
  337. }
  338. Listing Two
  339.  
  340. case WM_PAINT:
  341. {
  342. hdc = BeginPaint(hWnd, &ps);
  343. RECT rt;
  344. GetClientRect(hWnd, &rt);
  345.  
  346. int width = rt.right - rt.left;
  347. int height = rt.bottom - rt.top;
  348.  
  349. //Create compatible DC and a bitmap to render the image
  350. //--------------------------------------
  351. BITMAPINFO bmp_info;
  352. bmp_info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
  353. bmp_info.bmiHeader.biWidth = width;
  354. bmp_info.bmiHeader.biHeight = height;
  355. bmp_info.bmiHeader.biPlanes = 1;
  356. bmp_info.bmiHeader.biBitCount = 24;
  357. bmp_info.bmiHeader.biCompression = BI_RGB;
  358. bmp_info.bmiHeader.biSizeImage = 0;
  359. bmp_info.bmiHeader.biXPelsPerMeter = 0;
  360. bmp_info.bmiHeader.biYPelsPerMeter = 0;
  361. bmp_info.bmiHeader.biClrUsed = 0;
  362. bmp_info.bmiHeader.biClrImportant = 0;
  363.  
  364. HDC mem_dc = ::CreateCompatibleDC(hdc);
  365.  
  366. void* buf = 0;
  367.  
  368. HBITMAP bmp = ::CreateDIBSection(
  369. mem_dc,
  370. &bmp_info,
  371. DIB_RGB_COLORS,
  372. &buf,
  373. 0,
  374. 0
  375. );
  376.  
  377. HBITMAP temp = (HBITMAP)::SelectObject(mem_dc, bmp);
  378.  
  379. // Calculate image stride and size
  380. //---------------------------------
  381. unsigned char* rgb24 = (unsigned char*)buf;
  382. unsigned rgb24_stride = (width * 3 + 3) / 4 * 4;
  383. unsigned rgb24_size = height * rgb24_stride;
  384.  
  385. // Clear the image
  386. //---------------------------------
  387. memset(rgb24, 255, rgb24_size);
  388.  
  389.  
  390. // Create the energy distribution lookup table.
  391. // See description by Steve Gibson:
  392. http://grc.com/cttech.htm
  393.  
  394. // The class automatically normalizes the coefficients
  395. // in such a way that primary + 2*secondary + 3*tertiary =
  396. 1.0
  397. // Also, the input values are in range of 0...64, output
  398. ones
  399. // are 0...255.
  400. //
  401. // Try to play with different coefficients for the primary,
  402. // secondary, and tertiary distribution weights.
  403. // Steve Gibson recommends 1/3, 2/9, and 1/9, but it
  404. produces
  405. // too blur edges. It's better to increase the weight of the
  406. // primary and secondary pixel, then the text looks much
  407. crisper
  408. // with inconsiderably increased "color fringing".
  409. //---------------------------------
  410. //lcd_distribution_lut<ggo_gray8> lut(1.0/3.0, 2.0/9.0,
  411. 1.0/9.0);
  412. lcd_distribution_lut<ggo_gray8> lut(0.5, 0.25, 0.125);
  413.  
  414.  
  415. // Use a separate block to make sure the font will be
  416. created,
  417. // used with current DC and destroyed correctly (we need to
  418. // SelectObject(prev_font) before destroying)
  419. //---------------------------------
  420. {
  421. // Draw text
  422. //---------------------------------
  423. lcd_font fnt(hdc, "Arial", -20, false, true);
  424. draw_lcd_text(hdc,
  425. lut,
  426. 50 * 3, // X-positioning is also
  427. sub-pixel!
  428. 100,
  429. rgba(0,30,40, 230),
  430. "Hello World! Welcome to the perfectly
  431. LCD-optimized text rendering!",
  432. rgb24, rgb24_stride);
  433. }
  434.  
  435.  
  436. {
  437. // Draw "Copyright"
  438. //---------------------------------
  439. lcd_font fnt(hdc, "Arial", -12, false, false);
  440. draw_lcd_text(hdc, lut,
  441. 120 * 3, // X-positioning is also
  442. sub-pixel!
  443. 80,
  444. rgba(50,20,0, 220),
  445. L"\xA9 Maxim Shemanarev
  446. http://antigrain.com",
  447. rgb24, rgb24_stride);
  448. }
  449.  
  450.  
  451. {
  452. // Draw the big "o"
  453. //---------------------------------
  454. lcd_font fnt(hdc, "Arial", -100, false, false);
  455. draw_lcd_text(hdc, lut,
  456. 50 * 3, // X-positioning is also
  457. sub-pixel!
  458. 10,
  459. rgba(0,0,0),
  460. "O",
  461. rgb24, rgb24_stride);
  462. }
  463.  
  464.  
  465. // The drawing method assumes the R-G-B byte order,
  466. // so that we have to change it to the native Windows one
  467. // (B-G-R) to obtain the correct result.
  468. //-------------------------------------------------
  469. swap_rb(rgb24, width, height, rgb24_stride);
  470.  
  471.  
  472. // Display the image. If the image is B-G-R-A (32-bits per
  473. pixel)
  474. // one can use AlphaBlend instead of BitBlt. In case of
  475. AlphaBlend
  476. // one also should clear the image with zero alpha, i.e.
  477. rgba8(0,0,0,0)
  478. //-------------------------------------------------
  479. ::BitBlt(
  480. hdc,
  481. rt.left,
  482. rt.top,
  483. width,
  484. height,
  485. mem_dc,
  486. 0,
  487. 0,
  488. SRCCOPY
  489. );
  490.  
  491. // Free resources
  492. ::SelectObject(mem_dc, temp);
  493. ::DeleteObject(bmp);
  494. ::DeleteObject(mem_dc);
  495.  
  496. EndPaint(hWnd, &ps);
  497. }
  498. break;
  499.  
  500. Maxim Shemanarev heads the Anti-Grain Geometry (AGG) project, an open source, general purpose graphical
  501. toolkit written in standard and platform-independent C++.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement