Mercurial > sdl-ios-xcode
comparison src/video/windows/SDL_gdirender.c @ 5062:e8916fe9cfc8
Fixed bug #925
Changed "win32" to "windows"
author | Sam Lantinga <slouken@libsdl.org> |
---|---|
date | Thu, 20 Jan 2011 18:04:05 -0800 |
parents | src/video/win32/SDL_gdirender.c@aa8888658021 |
children | c2539ff054c8 |
comparison
equal
deleted
inserted
replaced
5061:9e9940eae455 | 5062:e8916fe9cfc8 |
---|---|
1 /* | |
2 SDL - Simple DirectMedia Layer | |
3 Copyright (C) 1997-2010 Sam Lantinga | |
4 | |
5 This library is free software; you can redistribute it and/or | |
6 modify it under the terms of the GNU Lesser General Public | |
7 License as published by the Free Software Foundation; either | |
8 version 2.1 of the License, or (at your option) any later version. | |
9 | |
10 This library is distributed in the hope that it will be useful, | |
11 but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
13 Lesser General Public License for more details. | |
14 | |
15 You should have received a copy of the GNU Lesser General Public | |
16 License along with this library; if not, write to the Free Software | |
17 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
18 | |
19 Sam Lantinga | |
20 slouken@libsdl.org | |
21 */ | |
22 #include "SDL_config.h" | |
23 | |
24 #if SDL_VIDEO_RENDER_GDI | |
25 | |
26 #include "SDL_windowsvideo.h" | |
27 #include "../SDL_rect_c.h" | |
28 #include "../SDL_yuv_sw_c.h" | |
29 #include "../SDL_alphamult.h" | |
30 | |
31 #ifdef _WIN32_WCE | |
32 #define NO_GETDIBBITS 1 | |
33 #endif | |
34 | |
35 /* GDI renderer implementation */ | |
36 | |
37 static SDL_Renderer *GDI_CreateRenderer(SDL_Window * window, Uint32 flags); | |
38 static int GDI_DisplayModeChanged(SDL_Renderer * renderer); | |
39 static int GDI_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture); | |
40 static int GDI_QueryTexturePixels(SDL_Renderer * renderer, | |
41 SDL_Texture * texture, void **pixels, | |
42 int *pitch); | |
43 static int GDI_SetTexturePalette(SDL_Renderer * renderer, | |
44 SDL_Texture * texture, | |
45 const SDL_Color * colors, int firstcolor, | |
46 int ncolors); | |
47 static int GDI_GetTexturePalette(SDL_Renderer * renderer, | |
48 SDL_Texture * texture, SDL_Color * colors, | |
49 int firstcolor, int ncolors); | |
50 static int GDI_SetTextureAlphaMod(SDL_Renderer * renderer, | |
51 SDL_Texture * texture); | |
52 static int GDI_SetTextureBlendMode(SDL_Renderer * renderer, | |
53 SDL_Texture * texture); | |
54 static int GDI_SetTextureScaleMode(SDL_Renderer * renderer, | |
55 SDL_Texture * texture); | |
56 static int GDI_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture, | |
57 const SDL_Rect * rect, const void *pixels, | |
58 int pitch); | |
59 static int GDI_LockTexture(SDL_Renderer * renderer, SDL_Texture * texture, | |
60 const SDL_Rect * rect, int markDirty, | |
61 void **pixels, int *pitch); | |
62 static void GDI_UnlockTexture(SDL_Renderer * renderer, SDL_Texture * texture); | |
63 static int GDI_SetDrawBlendMode(SDL_Renderer * renderer); | |
64 static int GDI_RenderDrawPoints(SDL_Renderer * renderer, | |
65 const SDL_Point * points, int count); | |
66 static int GDI_RenderDrawLines(SDL_Renderer * renderer, | |
67 const SDL_Point * points, int count); | |
68 static int GDI_RenderDrawRects(SDL_Renderer * renderer, | |
69 const SDL_Rect ** rects, int count); | |
70 static int GDI_RenderFillRects(SDL_Renderer * renderer, | |
71 const SDL_Rect ** rects, int count); | |
72 static int GDI_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture, | |
73 const SDL_Rect * srcrect, const SDL_Rect * dstrect); | |
74 static int GDI_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect, | |
75 Uint32 format, void * pixels, int pitch); | |
76 static int GDI_RenderWritePixels(SDL_Renderer * renderer, const SDL_Rect * rect, | |
77 Uint32 format, const void * pixels, int pitch); | |
78 static void GDI_RenderPresent(SDL_Renderer * renderer); | |
79 static void GDI_DestroyTexture(SDL_Renderer * renderer, | |
80 SDL_Texture * texture); | |
81 static void GDI_DestroyRenderer(SDL_Renderer * renderer); | |
82 | |
83 | |
84 SDL_RenderDriver GDI_RenderDriver = { | |
85 GDI_CreateRenderer, | |
86 { | |
87 "gdi", | |
88 (SDL_RENDERER_SINGLEBUFFER | SDL_RENDERER_PRESENTCOPY | | |
89 SDL_RENDERER_PRESENTFLIP2 | SDL_RENDERER_PRESENTFLIP3 | | |
90 SDL_RENDERER_PRESENTDISCARD | SDL_RENDERER_ACCELERATED), | |
91 (SDL_TEXTUREMODULATE_NONE | SDL_TEXTUREMODULATE_ALPHA), | |
92 (SDL_BLENDMODE_NONE | SDL_BLENDMODE_MASK), | |
93 (SDL_SCALEMODE_NONE | SDL_SCALEMODE_FAST), | |
94 14, | |
95 { | |
96 SDL_PIXELFORMAT_INDEX8, | |
97 SDL_PIXELFORMAT_RGB555, | |
98 SDL_PIXELFORMAT_RGB565, | |
99 SDL_PIXELFORMAT_RGB888, | |
100 SDL_PIXELFORMAT_BGR888, | |
101 SDL_PIXELFORMAT_ARGB8888, | |
102 SDL_PIXELFORMAT_RGBA8888, | |
103 SDL_PIXELFORMAT_ABGR8888, | |
104 SDL_PIXELFORMAT_BGRA8888, | |
105 SDL_PIXELFORMAT_YV12, | |
106 SDL_PIXELFORMAT_IYUV, | |
107 SDL_PIXELFORMAT_YUY2, | |
108 SDL_PIXELFORMAT_UYVY, | |
109 SDL_PIXELFORMAT_YVYU}, | |
110 0, | |
111 0} | |
112 }; | |
113 | |
114 typedef struct | |
115 { | |
116 HWND hwnd; | |
117 HDC window_hdc; | |
118 HDC render_hdc; | |
119 HDC memory_hdc; | |
120 HDC current_hdc; | |
121 #ifndef NO_GETDIBBITS | |
122 LPBITMAPINFO bmi; | |
123 #endif | |
124 HBITMAP hbm[3]; | |
125 int current_hbm; | |
126 SDL_DirtyRectList dirty; | |
127 SDL_bool makedirty; | |
128 } GDI_RenderData; | |
129 | |
130 typedef struct | |
131 { | |
132 SDL_SW_YUVTexture *yuv; | |
133 Uint32 format; | |
134 HPALETTE hpal; | |
135 HBITMAP hbm; | |
136 void *pixels; | |
137 int pitch; | |
138 SDL_bool premultiplied; | |
139 } GDI_TextureData; | |
140 | |
141 static void | |
142 UpdateYUVTextureData(SDL_Texture * texture) | |
143 { | |
144 GDI_TextureData *data = (GDI_TextureData *) texture->driverdata; | |
145 SDL_Rect rect; | |
146 | |
147 rect.x = 0; | |
148 rect.y = 0; | |
149 rect.w = texture->w; | |
150 rect.h = texture->h; | |
151 SDL_SW_CopyYUVToRGB(data->yuv, &rect, data->format, texture->w, | |
152 texture->h, data->pixels, data->pitch); | |
153 } | |
154 | |
155 void | |
156 GDI_AddRenderDriver(_THIS) | |
157 { | |
158 int i; | |
159 for (i = 0; i < _this->num_displays; ++i) { | |
160 SDL_AddRenderDriver(&_this->displays[i], &GDI_RenderDriver); | |
161 } | |
162 } | |
163 | |
164 SDL_Renderer * | |
165 GDI_CreateRenderer(SDL_Window * window, Uint32 flags) | |
166 { | |
167 SDL_WindowData *windowdata = (SDL_WindowData *) window->driverdata; | |
168 SDL_Renderer *renderer; | |
169 GDI_RenderData *data; | |
170 int bmi_size; | |
171 HBITMAP hbm; | |
172 int i, n; | |
173 | |
174 renderer = (SDL_Renderer *) SDL_calloc(1, sizeof(*renderer)); | |
175 if (!renderer) { | |
176 SDL_OutOfMemory(); | |
177 return NULL; | |
178 } | |
179 | |
180 data = (GDI_RenderData *) SDL_calloc(1, sizeof(*data)); | |
181 if (!data) { | |
182 GDI_DestroyRenderer(renderer); | |
183 SDL_OutOfMemory(); | |
184 return NULL; | |
185 } | |
186 | |
187 windowdata->videodata->render = RENDER_GDI; | |
188 | |
189 renderer->DisplayModeChanged = GDI_DisplayModeChanged; | |
190 renderer->CreateTexture = GDI_CreateTexture; | |
191 renderer->QueryTexturePixels = GDI_QueryTexturePixels; | |
192 renderer->SetTexturePalette = GDI_SetTexturePalette; | |
193 renderer->GetTexturePalette = GDI_GetTexturePalette; | |
194 renderer->SetTextureAlphaMod = GDI_SetTextureAlphaMod; | |
195 renderer->SetTextureBlendMode = GDI_SetTextureBlendMode; | |
196 renderer->SetTextureScaleMode = GDI_SetTextureScaleMode; | |
197 renderer->UpdateTexture = GDI_UpdateTexture; | |
198 renderer->LockTexture = GDI_LockTexture; | |
199 renderer->UnlockTexture = GDI_UnlockTexture; | |
200 renderer->SetDrawBlendMode = GDI_SetDrawBlendMode; | |
201 renderer->RenderDrawPoints = GDI_RenderDrawPoints; | |
202 renderer->RenderDrawLines = GDI_RenderDrawLines; | |
203 renderer->RenderDrawRects = GDI_RenderDrawRects; | |
204 renderer->RenderFillRects = GDI_RenderFillRects; | |
205 renderer->RenderCopy = GDI_RenderCopy; | |
206 renderer->RenderReadPixels = GDI_RenderReadPixels; | |
207 renderer->RenderWritePixels = GDI_RenderWritePixels; | |
208 renderer->RenderPresent = GDI_RenderPresent; | |
209 renderer->DestroyTexture = GDI_DestroyTexture; | |
210 renderer->DestroyRenderer = GDI_DestroyRenderer; | |
211 renderer->info = GDI_RenderDriver.info; | |
212 renderer->window = window; | |
213 renderer->driverdata = data; | |
214 | |
215 renderer->info.flags = SDL_RENDERER_ACCELERATED; | |
216 | |
217 data->hwnd = windowdata->hwnd; | |
218 data->window_hdc = windowdata->hdc; | |
219 data->render_hdc = CreateCompatibleDC(data->window_hdc); | |
220 data->memory_hdc = CreateCompatibleDC(data->window_hdc); | |
221 | |
222 #ifndef NO_GETDIBBITS | |
223 /* Fill in the compatible bitmap info */ | |
224 bmi_size = sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD); | |
225 data->bmi = (LPBITMAPINFO) SDL_calloc(1, bmi_size); | |
226 if (!data->bmi) { | |
227 GDI_DestroyRenderer(renderer); | |
228 SDL_OutOfMemory(); | |
229 return NULL; | |
230 } | |
231 data->bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); | |
232 | |
233 hbm = CreateCompatibleBitmap(data->window_hdc, 1, 1); | |
234 GetDIBits(data->window_hdc, hbm, 0, 1, NULL, data->bmi, DIB_RGB_COLORS); | |
235 GetDIBits(data->window_hdc, hbm, 0, 1, NULL, data->bmi, DIB_RGB_COLORS); | |
236 DeleteObject(hbm); | |
237 #endif | |
238 | |
239 if (flags & SDL_RENDERER_SINGLEBUFFER) { | |
240 renderer->info.flags |= | |
241 (SDL_RENDERER_SINGLEBUFFER | SDL_RENDERER_PRESENTCOPY); | |
242 n = 0; | |
243 } else if (flags & SDL_RENDERER_PRESENTFLIP2) { | |
244 renderer->info.flags |= SDL_RENDERER_PRESENTFLIP2; | |
245 n = 2; | |
246 } else if (flags & SDL_RENDERER_PRESENTFLIP3) { | |
247 renderer->info.flags |= SDL_RENDERER_PRESENTFLIP3; | |
248 n = 3; | |
249 } else { | |
250 renderer->info.flags |= SDL_RENDERER_PRESENTCOPY; | |
251 n = 1; | |
252 } | |
253 for (i = 0; i < n; ++i) { | |
254 data->hbm[i] = | |
255 CreateCompatibleBitmap(data->window_hdc, window->w, window->h); | |
256 if (!data->hbm[i]) { | |
257 GDI_DestroyRenderer(renderer); | |
258 WIN_SetError("CreateCompatibleBitmap()"); | |
259 return NULL; | |
260 } | |
261 } | |
262 if (n > 0) { | |
263 SelectObject(data->render_hdc, data->hbm[0]); | |
264 data->current_hdc = data->render_hdc; | |
265 data->makedirty = SDL_TRUE; | |
266 } else { | |
267 data->current_hdc = data->window_hdc; | |
268 data->makedirty = SDL_FALSE; | |
269 } | |
270 data->current_hbm = 0; | |
271 | |
272 #ifdef _WIN32_WCE | |
273 // check size for GDI fullscreen and rotate | |
274 if((window->flags & SDL_WINDOW_FULLSCREEN) && | |
275 GetSystemMetrics(SM_CXSCREEN) != GetSystemMetrics(SM_CYSCREEN) && | |
276 ((GetSystemMetrics(SM_CXSCREEN) < GetSystemMetrics(SM_CYSCREEN) && window->w > window->h) || | |
277 (GetSystemMetrics(SM_CXSCREEN) > GetSystemMetrics(SM_CYSCREEN) && window->w < window->h))) | |
278 { | |
279 int orientation = WINCE_GetDMOrientation(); | |
280 switch(orientation) | |
281 { | |
282 case DMDO_0: orientation = DMDO_90; break; | |
283 case DMDO_270: orientation = DMDO_180; break; | |
284 case DMDO_90: orientation = DMDO_0; break; | |
285 case DMDO_180: orientation = DMDO_270; break; | |
286 | |
287 default: | |
288 GDI_DestroyRenderer(renderer); | |
289 return NULL; | |
290 } | |
291 | |
292 if(0 > WINCE_SetDMOrientation(orientation)) | |
293 { | |
294 GDI_DestroyRenderer(renderer); | |
295 return NULL; | |
296 } | |
297 } | |
298 #endif | |
299 | |
300 return renderer; | |
301 } | |
302 | |
303 static int | |
304 GDI_DisplayModeChanged(SDL_Renderer * renderer) | |
305 { | |
306 GDI_RenderData *data = (GDI_RenderData *) renderer->driverdata; | |
307 SDL_Window *window = renderer->window; | |
308 int i, n; | |
309 | |
310 if (renderer->info.flags & SDL_RENDERER_SINGLEBUFFER) { | |
311 n = 0; | |
312 } else if (renderer->info.flags & SDL_RENDERER_PRESENTFLIP2) { | |
313 n = 2; | |
314 } else if (renderer->info.flags & SDL_RENDERER_PRESENTFLIP3) { | |
315 n = 3; | |
316 } else { | |
317 n = 1; | |
318 } | |
319 for (i = 0; i < n; ++i) { | |
320 if (data->hbm[i]) { | |
321 DeleteObject(data->hbm[i]); | |
322 data->hbm[i] = NULL; | |
323 } | |
324 } | |
325 for (i = 0; i < n; ++i) { | |
326 data->hbm[i] = | |
327 CreateCompatibleBitmap(data->window_hdc, window->w, window->h); | |
328 if (!data->hbm[i]) { | |
329 WIN_SetError("CreateCompatibleBitmap()"); | |
330 return -1; | |
331 } | |
332 } | |
333 if (n > 0) { | |
334 SelectObject(data->render_hdc, data->hbm[0]); | |
335 } | |
336 data->current_hbm = 0; | |
337 | |
338 return 0; | |
339 } | |
340 | |
341 static HBITMAP | |
342 GDI_CreateDIBSection(HDC hdc, int w, int h, int pitch, Uint32 format, | |
343 HPALETTE * hpal, void ** pixels) | |
344 { | |
345 int bmi_size; | |
346 LPBITMAPINFO bmi; | |
347 | |
348 bmi_size = sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD); | |
349 bmi = (LPBITMAPINFO) SDL_calloc(1, bmi_size); | |
350 if (!bmi) { | |
351 SDL_OutOfMemory(); | |
352 return NULL; | |
353 } | |
354 bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); | |
355 bmi->bmiHeader.biWidth = w; | |
356 bmi->bmiHeader.biHeight = -h; /* topdown bitmap */ | |
357 bmi->bmiHeader.biPlanes = 1; | |
358 bmi->bmiHeader.biSizeImage = h * pitch; | |
359 bmi->bmiHeader.biXPelsPerMeter = 0; | |
360 bmi->bmiHeader.biYPelsPerMeter = 0; | |
361 bmi->bmiHeader.biClrUsed = 0; | |
362 bmi->bmiHeader.biClrImportant = 0; | |
363 bmi->bmiHeader.biBitCount = SDL_BYTESPERPIXEL(format) * 8; | |
364 if (SDL_ISPIXELFORMAT_INDEXED(format)) { | |
365 bmi->bmiHeader.biCompression = BI_RGB; | |
366 if (hpal) { | |
367 int i, ncolors; | |
368 LOGPALETTE *palette; | |
369 | |
370 ncolors = (1 << SDL_BITSPERPIXEL(format)); | |
371 palette = | |
372 (LOGPALETTE *) SDL_malloc(sizeof(*palette) + | |
373 ncolors * sizeof(PALETTEENTRY)); | |
374 if (!palette) { | |
375 SDL_free(bmi); | |
376 SDL_OutOfMemory(); | |
377 return NULL; | |
378 } | |
379 palette->palVersion = 0x300; | |
380 palette->palNumEntries = ncolors; | |
381 for (i = 0; i < ncolors; ++i) { | |
382 palette->palPalEntry[i].peRed = 0xFF; | |
383 palette->palPalEntry[i].peGreen = 0xFF; | |
384 palette->palPalEntry[i].peBlue = 0xFF; | |
385 palette->palPalEntry[i].peFlags = 0; | |
386 } | |
387 *hpal = CreatePalette(palette); | |
388 SDL_free(palette); | |
389 } | |
390 } else { | |
391 int bpp; | |
392 Uint32 Rmask, Gmask, Bmask, Amask; | |
393 | |
394 bmi->bmiHeader.biCompression = BI_BITFIELDS; | |
395 SDL_PixelFormatEnumToMasks(format, &bpp, &Rmask, &Gmask, &Bmask, | |
396 &Amask); | |
397 ((Uint32 *) bmi->bmiColors)[0] = Rmask; | |
398 ((Uint32 *) bmi->bmiColors)[1] = Gmask; | |
399 ((Uint32 *) bmi->bmiColors)[2] = Bmask; | |
400 if (hpal) { | |
401 *hpal = NULL; | |
402 } | |
403 } | |
404 return CreateDIBSection(hdc, bmi, DIB_RGB_COLORS, pixels, NULL, 0); | |
405 } | |
406 | |
407 static int | |
408 GDI_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture) | |
409 { | |
410 GDI_RenderData *renderdata = (GDI_RenderData *) renderer->driverdata; | |
411 SDL_Window *window = renderer->window; | |
412 SDL_VideoDisplay *display = window->display; | |
413 GDI_TextureData *data; | |
414 | |
415 data = (GDI_TextureData *) SDL_calloc(1, sizeof(*data)); | |
416 if (!data) { | |
417 SDL_OutOfMemory(); | |
418 return -1; | |
419 } | |
420 | |
421 texture->driverdata = data; | |
422 | |
423 if (SDL_ISPIXELFORMAT_FOURCC(texture->format)) { | |
424 data->yuv = | |
425 SDL_SW_CreateYUVTexture(texture->format, texture->w, texture->h); | |
426 if (!data->yuv) { | |
427 return -1; | |
428 } | |
429 data->format = display->current_mode.format; | |
430 } else { | |
431 data->format = texture->format; | |
432 } | |
433 data->pitch = (texture->w * SDL_BYTESPERPIXEL(data->format)); | |
434 | |
435 if (data->yuv || texture->access == SDL_TEXTUREACCESS_STREAMING | |
436 || texture->format != display->current_mode.format) { | |
437 data->hbm = GDI_CreateDIBSection(renderdata->memory_hdc, | |
438 texture->w, texture->h, | |
439 data->pitch, data->format, | |
440 &data->hpal, &data->pixels); | |
441 } else { | |
442 data->hbm = CreateCompatibleBitmap(renderdata->window_hdc, | |
443 texture->w, texture->h); | |
444 } | |
445 if (!data->hbm) { | |
446 WIN_SetError("Couldn't create bitmap"); | |
447 return -1; | |
448 } | |
449 | |
450 return 0; | |
451 } | |
452 | |
453 static int | |
454 GDI_QueryTexturePixels(SDL_Renderer * renderer, SDL_Texture * texture, | |
455 void **pixels, int *pitch) | |
456 { | |
457 GDI_TextureData *data = (GDI_TextureData *) texture->driverdata; | |
458 | |
459 if (data->yuv) { | |
460 return SDL_SW_QueryYUVTexturePixels(data->yuv, pixels, pitch); | |
461 } else { | |
462 *pixels = data->pixels; | |
463 *pitch = data->pitch; | |
464 return 0; | |
465 } | |
466 } | |
467 | |
468 static int | |
469 GDI_SetTexturePalette(SDL_Renderer * renderer, SDL_Texture * texture, | |
470 const SDL_Color * colors, int firstcolor, int ncolors) | |
471 { | |
472 GDI_RenderData *renderdata = (GDI_RenderData *) renderer->driverdata; | |
473 GDI_TextureData *data = (GDI_TextureData *) texture->driverdata; | |
474 | |
475 if (data->yuv) { | |
476 SDL_SetError("YUV textures don't have a palette"); | |
477 return -1; | |
478 } else { | |
479 PALETTEENTRY entries[256]; | |
480 int i; | |
481 | |
482 for (i = 0; i < ncolors; ++i) { | |
483 entries[i].peRed = colors[i].r; | |
484 entries[i].peGreen = colors[i].g; | |
485 entries[i].peBlue = colors[i].b; | |
486 entries[i].peFlags = 0; | |
487 } | |
488 if (!SetPaletteEntries(data->hpal, firstcolor, ncolors, entries)) { | |
489 WIN_SetError("SetPaletteEntries()"); | |
490 return -1; | |
491 } | |
492 return 0; | |
493 } | |
494 } | |
495 | |
496 static int | |
497 GDI_GetTexturePalette(SDL_Renderer * renderer, SDL_Texture * texture, | |
498 SDL_Color * colors, int firstcolor, int ncolors) | |
499 { | |
500 GDI_TextureData *data = (GDI_TextureData *) texture->driverdata; | |
501 | |
502 if (data->yuv) { | |
503 SDL_SetError("YUV textures don't have a palette"); | |
504 return -1; | |
505 } else { | |
506 PALETTEENTRY entries[256]; | |
507 int i; | |
508 | |
509 if (!GetPaletteEntries(data->hpal, firstcolor, ncolors, entries)) { | |
510 WIN_SetError("GetPaletteEntries()"); | |
511 return -1; | |
512 } | |
513 for (i = 0; i < ncolors; ++i) { | |
514 colors[i].r = entries[i].peRed; | |
515 colors[i].g = entries[i].peGreen; | |
516 colors[i].b = entries[i].peBlue; | |
517 } | |
518 return 0; | |
519 } | |
520 } | |
521 | |
522 static int | |
523 GDI_SetTextureAlphaMod(SDL_Renderer * renderer, SDL_Texture * texture) | |
524 { | |
525 return 0; | |
526 } | |
527 | |
528 static int | |
529 GDI_SetTextureBlendMode(SDL_Renderer * renderer, SDL_Texture * texture) | |
530 { | |
531 GDI_TextureData *data = (GDI_TextureData *) texture->driverdata; | |
532 | |
533 switch (texture->blendMode) { | |
534 case SDL_BLENDMODE_NONE: | |
535 if (data->premultiplied) { | |
536 /* Crap, we've lost the original pixel data... *sigh* */ | |
537 } | |
538 return 0; | |
539 #ifndef _WIN32_WCE /* WinCE has no alphablend */ | |
540 case SDL_BLENDMODE_MASK: | |
541 case SDL_BLENDMODE_BLEND: | |
542 if (!data->premultiplied && data->pixels) { | |
543 switch (texture->format) { | |
544 case SDL_PIXELFORMAT_ARGB8888: | |
545 SDL_PreMultiplyAlphaARGB8888(texture->w, texture->h, | |
546 (Uint32 *) data->pixels, | |
547 data->pitch); | |
548 data->premultiplied = SDL_TRUE; | |
549 break; | |
550 case SDL_PIXELFORMAT_RGBA8888: | |
551 SDL_PreMultiplyAlphaRGBA8888(texture->w, texture->h, | |
552 (Uint32 *) data->pixels, | |
553 data->pitch); | |
554 data->premultiplied = SDL_TRUE; | |
555 break; | |
556 case SDL_PIXELFORMAT_ABGR8888: | |
557 SDL_PreMultiplyAlphaABGR8888(texture->w, texture->h, | |
558 (Uint32 *) data->pixels, | |
559 data->pitch); | |
560 data->premultiplied = SDL_TRUE; | |
561 break; | |
562 case SDL_PIXELFORMAT_BGRA8888: | |
563 SDL_PreMultiplyAlphaBGRA8888(texture->w, texture->h, | |
564 (Uint32 *) data->pixels, | |
565 data->pitch); | |
566 data->premultiplied = SDL_TRUE; | |
567 break; | |
568 } | |
569 } | |
570 return 0; | |
571 #endif | |
572 default: | |
573 SDL_Unsupported(); | |
574 texture->blendMode = SDL_BLENDMODE_NONE; | |
575 return -1; | |
576 } | |
577 } | |
578 | |
579 static int | |
580 GDI_SetTextureScaleMode(SDL_Renderer * renderer, SDL_Texture * texture) | |
581 { | |
582 switch (texture->scaleMode) { | |
583 case SDL_SCALEMODE_NONE: | |
584 case SDL_SCALEMODE_FAST: | |
585 return 0; | |
586 case SDL_SCALEMODE_SLOW: | |
587 case SDL_SCALEMODE_BEST: | |
588 SDL_Unsupported(); | |
589 texture->scaleMode = SDL_SCALEMODE_FAST; | |
590 return -1; | |
591 default: | |
592 SDL_Unsupported(); | |
593 texture->scaleMode = SDL_SCALEMODE_NONE; | |
594 return -1; | |
595 } | |
596 return 0; | |
597 } | |
598 | |
599 static int | |
600 GDI_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture, | |
601 const SDL_Rect * rect, const void *pixels, int pitch) | |
602 { | |
603 GDI_TextureData *data = (GDI_TextureData *) texture->driverdata; | |
604 | |
605 if (data->yuv) { | |
606 if (SDL_SW_UpdateYUVTexture(data->yuv, rect, pixels, pitch) < 0) { | |
607 return -1; | |
608 } | |
609 UpdateYUVTextureData(texture); | |
610 return 0; | |
611 } else { | |
612 GDI_RenderData *renderdata = (GDI_RenderData *) renderer->driverdata; | |
613 | |
614 if (data->pixels) { | |
615 Uint8 *src, *dst; | |
616 int row; | |
617 size_t length; | |
618 | |
619 src = (Uint8 *) pixels; | |
620 dst = | |
621 (Uint8 *) data->pixels + rect->y * data->pitch + | |
622 rect->x * SDL_BYTESPERPIXEL(texture->format); | |
623 length = rect->w * SDL_BYTESPERPIXEL(texture->format); | |
624 for (row = 0; row < rect->h; ++row) { | |
625 SDL_memcpy(dst, src, length); | |
626 src += pitch; | |
627 dst += data->pitch; | |
628 } | |
629 if (data->premultiplied) { | |
630 Uint32 *pixels = | |
631 (Uint32 *) data->pixels + rect->y * (data->pitch / 4) + | |
632 rect->x; | |
633 switch (texture->format) { | |
634 case SDL_PIXELFORMAT_ARGB8888: | |
635 SDL_PreMultiplyAlphaARGB8888(rect->w, rect->h, pixels, | |
636 data->pitch); | |
637 break; | |
638 case SDL_PIXELFORMAT_RGBA8888: | |
639 SDL_PreMultiplyAlphaRGBA8888(rect->w, rect->h, pixels, | |
640 data->pitch); | |
641 break; | |
642 case SDL_PIXELFORMAT_ABGR8888: | |
643 SDL_PreMultiplyAlphaABGR8888(rect->w, rect->h, pixels, | |
644 data->pitch); | |
645 break; | |
646 case SDL_PIXELFORMAT_BGRA8888: | |
647 SDL_PreMultiplyAlphaBGRA8888(rect->w, rect->h, pixels, | |
648 data->pitch); | |
649 break; | |
650 } | |
651 } | |
652 } else if (rect->w == texture->w && pitch == data->pitch) { | |
653 #ifndef NO_GETDIBBITS | |
654 if (!SetDIBits | |
655 (renderdata->window_hdc, data->hbm, rect->y, rect->h, pixels, | |
656 renderdata->bmi, DIB_RGB_COLORS)) { | |
657 WIN_SetError("SetDIBits()"); | |
658 return -1; | |
659 } | |
660 #else | |
661 SDL_SetError("FIXME: Update Texture"); | |
662 return -1; | |
663 #endif | |
664 } else { | |
665 SDL_SetError | |
666 ("FIXME: Need to allocate temporary memory and do GetDIBits() followed by SetDIBits(), since we can only set blocks of scanlines at a time"); | |
667 return -1; | |
668 } | |
669 return 0; | |
670 } | |
671 } | |
672 | |
673 static int | |
674 GDI_LockTexture(SDL_Renderer * renderer, SDL_Texture * texture, | |
675 const SDL_Rect * rect, int markDirty, void **pixels, | |
676 int *pitch) | |
677 { | |
678 GDI_TextureData *data = (GDI_TextureData *) texture->driverdata; | |
679 | |
680 if (data->yuv) { | |
681 return SDL_SW_LockYUVTexture(data->yuv, rect, markDirty, pixels, | |
682 pitch); | |
683 } else if (data->pixels) { | |
684 #ifndef _WIN32_WCE | |
685 /* WinCE has no GdiFlush */ | |
686 GdiFlush(); | |
687 #endif | |
688 *pixels = | |
689 (void *) ((Uint8 *) data->pixels + rect->y * data->pitch + | |
690 rect->x * SDL_BYTESPERPIXEL(texture->format)); | |
691 *pitch = data->pitch; | |
692 return 0; | |
693 } else { | |
694 SDL_SetError("No pixels available"); | |
695 return -1; | |
696 } | |
697 } | |
698 | |
699 static void | |
700 GDI_UnlockTexture(SDL_Renderer * renderer, SDL_Texture * texture) | |
701 { | |
702 GDI_TextureData *data = (GDI_TextureData *) texture->driverdata; | |
703 | |
704 if (data->yuv) { | |
705 SDL_SW_UnlockYUVTexture(data->yuv); | |
706 UpdateYUVTextureData(texture); | |
707 } | |
708 } | |
709 | |
710 static int | |
711 GDI_SetDrawBlendMode(SDL_Renderer * renderer) | |
712 { | |
713 switch (renderer->blendMode) { | |
714 case SDL_BLENDMODE_NONE: | |
715 return 0; | |
716 default: | |
717 SDL_Unsupported(); | |
718 renderer->blendMode = SDL_BLENDMODE_NONE; | |
719 return -1; | |
720 } | |
721 } | |
722 | |
723 static int | |
724 GDI_RenderDrawPoints(SDL_Renderer * renderer, const SDL_Point * points, | |
725 int count) | |
726 { | |
727 GDI_RenderData *data = (GDI_RenderData *) renderer->driverdata; | |
728 int i; | |
729 COLORREF color; | |
730 | |
731 if (data->makedirty) { | |
732 /* Get the smallest rectangle that contains everything */ | |
733 SDL_Window *window = renderer->window; | |
734 SDL_Rect rect; | |
735 | |
736 rect.x = 0; | |
737 rect.y = 0; | |
738 rect.w = window->w; | |
739 rect.h = window->h; | |
740 if (!SDL_EnclosePoints(points, count, &rect, &rect)) { | |
741 /* Nothing to draw */ | |
742 return 0; | |
743 } | |
744 | |
745 SDL_AddDirtyRect(&data->dirty, &rect); | |
746 } | |
747 | |
748 color = RGB(renderer->r, renderer->g, renderer->b); | |
749 for (i = 0; i < count; ++i) { | |
750 SetPixel(data->current_hdc, points[i].x, points[i].y, color); | |
751 } | |
752 | |
753 return 0; | |
754 } | |
755 | |
756 static int | |
757 GDI_RenderDrawLines(SDL_Renderer * renderer, const SDL_Point * points, | |
758 int count) | |
759 { | |
760 GDI_RenderData *data = (GDI_RenderData *) renderer->driverdata; | |
761 HPEN pen; | |
762 BOOL status; | |
763 | |
764 if (data->makedirty) { | |
765 /* Get the smallest rectangle that contains everything */ | |
766 SDL_Window *window = renderer->window; | |
767 SDL_Rect clip, rect; | |
768 | |
769 clip.x = 0; | |
770 clip.y = 0; | |
771 clip.w = window->w; | |
772 clip.h = window->h; | |
773 SDL_EnclosePoints(points, count, NULL, &rect); | |
774 if (!SDL_IntersectRect(&rect, &clip, &rect)) { | |
775 /* Nothing to draw */ | |
776 return 0; | |
777 } | |
778 | |
779 SDL_AddDirtyRect(&data->dirty, &rect); | |
780 } | |
781 | |
782 /* Should we cache the pen? .. it looks like GDI does for us. :) */ | |
783 pen = CreatePen(PS_SOLID, 1, RGB(renderer->r, renderer->g, renderer->b)); | |
784 SelectObject(data->current_hdc, pen); | |
785 { | |
786 LPPOINT p = SDL_stack_alloc(POINT, count); | |
787 int i; | |
788 | |
789 for (i = 0; i < count; ++i) { | |
790 p[i].x = points[i].x; | |
791 p[i].y = points[i].y; | |
792 } | |
793 status = Polyline(data->current_hdc, p, count); | |
794 SDL_stack_free(p); | |
795 } | |
796 DeleteObject(pen); | |
797 | |
798 /* Need to close the endpoint of the line */ | |
799 if (points[0].x != points[count-1].x || points[0].y != points[count-1].y) { | |
800 SetPixel(data->current_hdc, points[count-1].x, points[count-1].y, | |
801 RGB(renderer->r, renderer->g, renderer->b)); | |
802 } | |
803 | |
804 if (!status) { | |
805 WIN_SetError("Polyline()"); | |
806 return -1; | |
807 } | |
808 return 0; | |
809 } | |
810 | |
811 static int | |
812 GDI_RenderDrawRects(SDL_Renderer * renderer, const SDL_Rect ** rects, | |
813 int count) | |
814 { | |
815 GDI_RenderData *data = (GDI_RenderData *) renderer->driverdata; | |
816 HPEN pen; | |
817 POINT vertices[5]; | |
818 int i, status = 1; | |
819 | |
820 if (data->makedirty) { | |
821 SDL_Window *window = renderer->window; | |
822 SDL_Rect clip, rect; | |
823 | |
824 clip.x = 0; | |
825 clip.y = 0; | |
826 clip.w = window->w; | |
827 clip.h = window->h; | |
828 | |
829 for (i = 0; i < count; ++i) { | |
830 if (SDL_IntersectRect(rects[i], &clip, &rect)) { | |
831 SDL_AddDirtyRect(&data->dirty, &rect); | |
832 } | |
833 } | |
834 } | |
835 | |
836 /* Should we cache the pen? .. it looks like GDI does for us. :) */ | |
837 pen = CreatePen(PS_SOLID, 1, RGB(renderer->r, renderer->g, renderer->b)); | |
838 SelectObject(data->current_hdc, pen); | |
839 for (i = 0; i < count; ++i) { | |
840 const SDL_Rect *rect = rects[i]; | |
841 | |
842 vertices[0].x = rect->x; | |
843 vertices[0].y = rect->y; | |
844 | |
845 vertices[1].x = rect->x+rect->w-1; | |
846 vertices[1].y = rect->y; | |
847 | |
848 vertices[2].x = rect->x+rect->w-1; | |
849 vertices[2].y = rect->y+rect->h-1; | |
850 | |
851 vertices[3].x = rect->x; | |
852 vertices[3].y = rect->y+rect->h-1; | |
853 | |
854 vertices[4].x = rect->x; | |
855 vertices[4].y = rect->y; | |
856 | |
857 status &= Polyline(data->current_hdc, vertices, 5); | |
858 } | |
859 DeleteObject(pen); | |
860 | |
861 if (!status) { | |
862 WIN_SetError("Polyline()"); | |
863 return -1; | |
864 } | |
865 return 0; | |
866 } | |
867 | |
868 static int | |
869 GDI_RenderFillRects(SDL_Renderer * renderer, const SDL_Rect ** rects, | |
870 int count) | |
871 { | |
872 GDI_RenderData *data = (GDI_RenderData *) renderer->driverdata; | |
873 RECT rc; | |
874 HBRUSH brush; | |
875 int i, status = 1; | |
876 | |
877 if (data->makedirty) { | |
878 SDL_Window *window = renderer->window; | |
879 SDL_Rect clip, rect; | |
880 | |
881 clip.x = 0; | |
882 clip.y = 0; | |
883 clip.w = window->w; | |
884 clip.h = window->h; | |
885 | |
886 for (i = 0; i < count; ++i) { | |
887 if (SDL_IntersectRect(rects[i], &clip, &rect)) { | |
888 SDL_AddDirtyRect(&data->dirty, &rect); | |
889 } | |
890 } | |
891 } | |
892 | |
893 /* Should we cache the brushes? .. it looks like GDI does for us. :) */ | |
894 brush = CreateSolidBrush(RGB(renderer->r, renderer->g, renderer->b)); | |
895 SelectObject(data->current_hdc, brush); | |
896 for (i = 0; i < count; ++i) { | |
897 const SDL_Rect *rect = rects[i]; | |
898 | |
899 rc.left = rect->x; | |
900 rc.top = rect->y; | |
901 rc.right = rect->x + rect->w; | |
902 rc.bottom = rect->y + rect->h; | |
903 | |
904 status &= FillRect(data->current_hdc, &rc, brush); | |
905 } | |
906 DeleteObject(brush); | |
907 | |
908 if (!status) { | |
909 WIN_SetError("FillRect()"); | |
910 return -1; | |
911 } | |
912 return 0; | |
913 } | |
914 | |
915 static int | |
916 GDI_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture, | |
917 const SDL_Rect * srcrect, const SDL_Rect * dstrect) | |
918 { | |
919 GDI_RenderData *data = (GDI_RenderData *) renderer->driverdata; | |
920 GDI_TextureData *texturedata = (GDI_TextureData *) texture->driverdata; | |
921 | |
922 if (data->makedirty) { | |
923 SDL_AddDirtyRect(&data->dirty, dstrect); | |
924 } | |
925 | |
926 SelectObject(data->memory_hdc, texturedata->hbm); | |
927 if (texturedata->hpal) { | |
928 SelectPalette(data->memory_hdc, texturedata->hpal, TRUE); | |
929 RealizePalette(data->memory_hdc); | |
930 } | |
931 if (texture->blendMode & (SDL_BLENDMODE_MASK | SDL_BLENDMODE_BLEND)) { | |
932 #ifdef _WIN32_WCE | |
933 SDL_SetError("Texture has blendmode not supported under WinCE"); | |
934 return -1; | |
935 #else | |
936 BLENDFUNCTION blendFunc = { | |
937 AC_SRC_OVER, | |
938 0, | |
939 texture->a, | |
940 AC_SRC_ALPHA | |
941 }; | |
942 if (!AlphaBlend | |
943 (data->current_hdc, dstrect->x, dstrect->y, dstrect->w, | |
944 dstrect->h, data->memory_hdc, srcrect->x, srcrect->y, srcrect->w, | |
945 srcrect->h, blendFunc)) { | |
946 WIN_SetError("AlphaBlend()"); | |
947 return -1; | |
948 } | |
949 #endif | |
950 } else { | |
951 if (srcrect->w == dstrect->w && srcrect->h == dstrect->h) { | |
952 if (!BitBlt | |
953 (data->current_hdc, dstrect->x, dstrect->y, dstrect->w, | |
954 srcrect->h, data->memory_hdc, srcrect->x, srcrect->y, | |
955 SRCCOPY)) { | |
956 WIN_SetError("BitBlt()"); | |
957 return -1; | |
958 } | |
959 } else { | |
960 if (!StretchBlt | |
961 (data->current_hdc, dstrect->x, dstrect->y, dstrect->w, | |
962 dstrect->h, data->memory_hdc, srcrect->x, srcrect->y, | |
963 srcrect->w, srcrect->h, SRCCOPY)) { | |
964 WIN_SetError("StretchBlt()"); | |
965 return -1; | |
966 } | |
967 } | |
968 } | |
969 return 0; | |
970 } | |
971 | |
972 static int | |
973 GDI_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect, | |
974 Uint32 format, void * pixels, int pitch) | |
975 { | |
976 GDI_RenderData *renderdata = (GDI_RenderData *) renderer->driverdata; | |
977 SDL_Window *window = renderer->window; | |
978 SDL_VideoDisplay *display = window->display; | |
979 struct { | |
980 HBITMAP hbm; | |
981 void *pixels; | |
982 int pitch; | |
983 Uint32 format; | |
984 } data; | |
985 | |
986 data.format = display->current_mode.format; | |
987 data.pitch = (rect->w * SDL_BYTESPERPIXEL(data.format)); | |
988 | |
989 data.hbm = GDI_CreateDIBSection(renderdata->memory_hdc, rect->w, rect->h, | |
990 data.pitch, data.format, NULL, | |
991 &data.pixels); | |
992 if (!data.hbm) { | |
993 WIN_SetError("Couldn't create bitmap"); | |
994 return -1; | |
995 } | |
996 | |
997 SelectObject(renderdata->memory_hdc, data.hbm); | |
998 if (!BitBlt(renderdata->memory_hdc, 0, 0, rect->w, rect->h, | |
999 renderdata->current_hdc, rect->x, rect->y, SRCCOPY)) { | |
1000 WIN_SetError("BitBlt()"); | |
1001 DeleteObject(data.hbm); | |
1002 return -1; | |
1003 } | |
1004 | |
1005 SDL_ConvertPixels(rect->w, rect->h, | |
1006 data.format, data.pixels, data.pitch, | |
1007 format, pixels, pitch); | |
1008 | |
1009 DeleteObject(data.hbm); | |
1010 return 0; | |
1011 } | |
1012 | |
1013 static int | |
1014 GDI_RenderWritePixels(SDL_Renderer * renderer, const SDL_Rect * rect, | |
1015 Uint32 format, const void * pixels, int pitch) | |
1016 { | |
1017 GDI_RenderData *renderdata = (GDI_RenderData *) renderer->driverdata; | |
1018 SDL_Window *window = renderer->window; | |
1019 SDL_VideoDisplay *display = window->display; | |
1020 struct { | |
1021 HBITMAP hbm; | |
1022 void *pixels; | |
1023 int pitch; | |
1024 Uint32 format; | |
1025 } data; | |
1026 | |
1027 data.format = display->current_mode.format; | |
1028 data.pitch = (rect->w * SDL_BYTESPERPIXEL(data.format)); | |
1029 | |
1030 data.hbm = GDI_CreateDIBSection(renderdata->memory_hdc, rect->w, rect->h, | |
1031 data.pitch, data.format, | |
1032 NULL, &data.pixels); | |
1033 if (!data.hbm) { | |
1034 WIN_SetError("Couldn't create bitmap"); | |
1035 return -1; | |
1036 } | |
1037 | |
1038 SDL_ConvertPixels(rect->w, rect->h, format, pixels, pitch, | |
1039 data.format, data.pixels, data.pitch); | |
1040 | |
1041 SelectObject(renderdata->memory_hdc, data.hbm); | |
1042 if (!BitBlt(renderdata->current_hdc, rect->x, rect->y, rect->w, rect->h, | |
1043 renderdata->memory_hdc, 0, 0, SRCCOPY)) { | |
1044 WIN_SetError("BitBlt()"); | |
1045 DeleteObject(data.hbm); | |
1046 return -1; | |
1047 } | |
1048 | |
1049 DeleteObject(data.hbm); | |
1050 return 0; | |
1051 } | |
1052 | |
1053 static void | |
1054 GDI_RenderPresent(SDL_Renderer * renderer) | |
1055 { | |
1056 GDI_RenderData *data = (GDI_RenderData *) renderer->driverdata; | |
1057 SDL_DirtyRect *dirty; | |
1058 | |
1059 /* Send the data to the display */ | |
1060 if (!(renderer->info.flags & SDL_RENDERER_SINGLEBUFFER)) { | |
1061 for (dirty = data->dirty.list; dirty; dirty = dirty->next) { | |
1062 const SDL_Rect *rect = &dirty->rect; | |
1063 BitBlt(data->window_hdc, rect->x, rect->y, rect->w, rect->h, | |
1064 data->render_hdc, rect->x, rect->y, SRCCOPY); | |
1065 } | |
1066 SDL_ClearDirtyRects(&data->dirty); | |
1067 } | |
1068 | |
1069 /* Update the flipping chain, if any */ | |
1070 if (renderer->info.flags & SDL_RENDERER_PRESENTFLIP2) { | |
1071 data->current_hbm = (data->current_hbm + 1) % 2; | |
1072 SelectObject(data->render_hdc, data->hbm[data->current_hbm]); | |
1073 } else if (renderer->info.flags & SDL_RENDERER_PRESENTFLIP3) { | |
1074 data->current_hbm = (data->current_hbm + 1) % 3; | |
1075 SelectObject(data->render_hdc, data->hbm[data->current_hbm]); | |
1076 } | |
1077 } | |
1078 | |
1079 static void | |
1080 GDI_DestroyTexture(SDL_Renderer * renderer, SDL_Texture * texture) | |
1081 { | |
1082 GDI_TextureData *data = (GDI_TextureData *) texture->driverdata; | |
1083 | |
1084 if (!data) { | |
1085 return; | |
1086 } | |
1087 if (data->yuv) { | |
1088 SDL_SW_DestroyYUVTexture(data->yuv); | |
1089 } | |
1090 if (data->hpal) { | |
1091 DeleteObject(data->hpal); | |
1092 } | |
1093 if (data->hbm) { | |
1094 DeleteObject(data->hbm); | |
1095 } | |
1096 SDL_free(data); | |
1097 texture->driverdata = NULL; | |
1098 } | |
1099 | |
1100 static void | |
1101 GDI_DestroyRenderer(SDL_Renderer * renderer) | |
1102 { | |
1103 GDI_RenderData *data = (GDI_RenderData *) renderer->driverdata; | |
1104 int i; | |
1105 | |
1106 if (data) { | |
1107 DeleteDC(data->render_hdc); | |
1108 DeleteDC(data->memory_hdc); | |
1109 #ifndef NO_GETDIBBITS | |
1110 if (data->bmi) { | |
1111 SDL_free(data->bmi); | |
1112 } | |
1113 #endif | |
1114 for (i = 0; i < SDL_arraysize(data->hbm); ++i) { | |
1115 if (data->hbm[i]) { | |
1116 DeleteObject(data->hbm[i]); | |
1117 } | |
1118 } | |
1119 SDL_FreeDirtyRects(&data->dirty); | |
1120 SDL_free(data); | |
1121 } | |
1122 SDL_free(renderer); | |
1123 } | |
1124 | |
1125 #endif /* SDL_VIDEO_RENDER_GDI */ | |
1126 | |
1127 /* vi: set ts=4 sw=4 expandtab: */ |