change SDL 1.2 to SDL 2.0
[platform/upstream/SDL.git] / src / video / SDL_bmp.c
1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2016 Sam Lantinga <slouken@libsdl.org>
4
5   This software is provided 'as-is', without any express or implied
6   warranty.  In no event will the authors be held liable for any damages
7   arising from the use of this software.
8
9   Permission is granted to anyone to use this software for any purpose,
10   including commercial applications, and to alter it and redistribute it
11   freely, subject to the following restrictions:
12
13   1. The origin of this software must not be misrepresented; you must not
14      claim that you wrote the original software. If you use this software
15      in a product, an acknowledgment in the product documentation would be
16      appreciated but is not required.
17   2. Altered source versions must be plainly marked as such, and must not be
18      misrepresented as being the original software.
19   3. This notice may not be removed or altered from any source distribution.
20 */
21 #include "../SDL_internal.h"
22
23 /*
24    Code to load and save surfaces in Windows BMP format.
25
26    Why support BMP format?  Well, it's a native format for Windows, and
27    most image processing programs can read and write it.  It would be nice
28    to be able to have at least one image format that we can natively load
29    and save, and since PNG is so complex that it would bloat the library,
30    BMP is a good alternative.
31
32    This code currently supports Win32 DIBs in uncompressed 8 and 24 bpp.
33 */
34
35 #include "SDL_video.h"
36 #include "SDL_assert.h"
37 #include "SDL_endian.h"
38 #include "SDL_pixels_c.h"
39
40 #define SAVE_32BIT_BMP
41
42 /* Compression encodings for BMP files */
43 #ifndef BI_RGB
44 #define BI_RGB      0
45 #define BI_RLE8     1
46 #define BI_RLE4     2
47 #define BI_BITFIELDS    3
48 #endif
49
50
51 static void CorrectAlphaChannel(SDL_Surface *surface)
52 {
53     /* Check to see if there is any alpha channel data */
54     SDL_bool hasAlpha = SDL_FALSE;
55 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
56     int alphaChannelOffset = 0;
57 #else
58     int alphaChannelOffset = 3;
59 #endif
60     Uint8 *alpha = ((Uint8*)surface->pixels) + alphaChannelOffset;
61     Uint8 *end = alpha + surface->h * surface->pitch;
62
63     while (alpha < end) {
64         if (*alpha != 0) {
65             hasAlpha = SDL_TRUE;
66             break;
67         }
68         alpha += 4;
69     }
70
71     if (!hasAlpha) {
72         alpha = ((Uint8*)surface->pixels) + alphaChannelOffset;
73         while (alpha < end) {
74             *alpha = SDL_ALPHA_OPAQUE;
75             alpha += 4;
76         }
77     }
78 }
79
80 SDL_Surface *
81 SDL_LoadBMP_RW(SDL_RWops * src, int freesrc)
82 {
83     SDL_bool was_error;
84     Sint64 fp_offset = 0;
85     int bmpPitch;
86     int i, pad;
87     SDL_Surface *surface;
88     Uint32 Rmask = 0;
89     Uint32 Gmask = 0;
90     Uint32 Bmask = 0;
91     Uint32 Amask = 0;
92     SDL_Palette *palette;
93     Uint8 *bits;
94     Uint8 *top, *end;
95     SDL_bool topDown;
96     int ExpandBMP;
97     SDL_bool haveRGBMasks = SDL_FALSE;
98     SDL_bool haveAlphaMask = SDL_FALSE;
99     SDL_bool correctAlpha = SDL_FALSE;
100
101     /* The Win32 BMP file header (14 bytes) */
102     char magic[2];
103     /* Uint32 bfSize = 0; */
104     /* Uint16 bfReserved1 = 0; */
105     /* Uint16 bfReserved2 = 0; */
106     Uint32 bfOffBits = 0;
107
108     /* The Win32 BITMAPINFOHEADER struct (40 bytes) */
109     Uint32 biSize = 0;
110     Sint32 biWidth = 0;
111     Sint32 biHeight = 0;
112     /* Uint16 biPlanes = 0; */
113     Uint16 biBitCount = 0;
114     Uint32 biCompression = 0;
115     /* Uint32 biSizeImage = 0; */
116     /* Sint32 biXPelsPerMeter = 0; */
117     /* Sint32 biYPelsPerMeter = 0; */
118     Uint32 biClrUsed = 0;
119     /* Uint32 biClrImportant = 0; */
120
121     /* Make sure we are passed a valid data source */
122     surface = NULL;
123     was_error = SDL_FALSE;
124     if (src == NULL) {
125         was_error = SDL_TRUE;
126         goto done;
127     }
128
129     /* Read in the BMP file header */
130     fp_offset = SDL_RWtell(src);
131     SDL_ClearError();
132     if (SDL_RWread(src, magic, 1, 2) != 2) {
133         SDL_Error(SDL_EFREAD);
134         was_error = SDL_TRUE;
135         goto done;
136     }
137     if (SDL_strncmp(magic, "BM", 2) != 0) {
138         SDL_SetError("File is not a Windows BMP file");
139         was_error = SDL_TRUE;
140         goto done;
141     }
142     /* bfSize = */ SDL_ReadLE32(src);
143     /* bfReserved1 = */ SDL_ReadLE16(src);
144     /* bfReserved2 = */ SDL_ReadLE16(src);
145     bfOffBits = SDL_ReadLE32(src);
146
147     /* Read the Win32 BITMAPINFOHEADER */
148     biSize = SDL_ReadLE32(src);
149     if (biSize == 12) {   /* really old BITMAPCOREHEADER */
150         biWidth = (Uint32) SDL_ReadLE16(src);
151         biHeight = (Uint32) SDL_ReadLE16(src);
152         /* biPlanes = */ SDL_ReadLE16(src);
153         biBitCount = SDL_ReadLE16(src);
154         biCompression = BI_RGB;
155     } else if (biSize >= 40) {  /* some version of BITMAPINFOHEADER */
156         Uint32 headerSize;
157         biWidth = SDL_ReadLE32(src);
158         biHeight = SDL_ReadLE32(src);
159         /* biPlanes = */ SDL_ReadLE16(src);
160         biBitCount = SDL_ReadLE16(src);
161         biCompression = SDL_ReadLE32(src);
162         /* biSizeImage = */ SDL_ReadLE32(src);
163         /* biXPelsPerMeter = */ SDL_ReadLE32(src);
164         /* biYPelsPerMeter = */ SDL_ReadLE32(src);
165         biClrUsed = SDL_ReadLE32(src);
166         /* biClrImportant = */ SDL_ReadLE32(src);
167
168         /* 64 == BITMAPCOREHEADER2, an incompatible OS/2 2.x extension. Skip this stuff for now. */
169         if (biSize == 64) {
170             /* ignore these extra fields. */
171             if (biCompression == BI_BITFIELDS) {
172                 /* this value is actually huffman compression in this variant. */
173                 SDL_SetError("Compressed BMP files not supported");
174                 was_error = SDL_TRUE;
175                 goto done;
176             }
177         } else {
178             /* This is complicated. If compression is BI_BITFIELDS, then
179                we have 3 DWORDS that specify the RGB masks. This is either
180                stored here in an BITMAPV2INFOHEADER (which only differs in
181                that it adds these RGB masks) and biSize >= 52, or we've got
182                these masks stored in the exact same place, but strictly
183                speaking, this is the bmiColors field in BITMAPINFO immediately
184                following the legacy v1 info header, just past biSize. */
185             if (biCompression == BI_BITFIELDS) {
186                 haveRGBMasks = SDL_TRUE;
187                 Rmask = SDL_ReadLE32(src);
188                 Gmask = SDL_ReadLE32(src);
189                 Bmask = SDL_ReadLE32(src);
190
191                 /* ...v3 adds an alpha mask. */
192                 if (biSize >= 56) {  /* BITMAPV3INFOHEADER; adds alpha mask */
193                     haveAlphaMask = SDL_TRUE;
194                     Amask = SDL_ReadLE32(src);
195                 }
196             } else {
197                 /* the mask fields are ignored for v2+ headers if not BI_BITFIELD. */
198                 if (biSize >= 52) {  /* BITMAPV2INFOHEADER; adds RGB masks */
199                     /*Rmask = */ SDL_ReadLE32(src);
200                     /*Gmask = */ SDL_ReadLE32(src);
201                     /*Bmask = */ SDL_ReadLE32(src);
202                 }
203                 if (biSize >= 56) {  /* BITMAPV3INFOHEADER; adds alpha mask */
204                     /*Amask = */ SDL_ReadLE32(src);
205                 }
206             }
207
208             /* Insert other fields here; Wikipedia and MSDN say we're up to
209                v5 of this header, but we ignore those for now (they add gamma,
210                color spaces, etc). Ignoring the weird OS/2 2.x format, we
211                currently parse up to v3 correctly (hopefully!). */
212         }
213
214         /* skip any header bytes we didn't handle... */
215         headerSize = (Uint32) (SDL_RWtell(src) - (fp_offset + 14));
216         if (biSize > headerSize) {
217             SDL_RWseek(src, (biSize - headerSize), RW_SEEK_CUR);
218         }
219     }
220     if (biHeight < 0) {
221         topDown = SDL_TRUE;
222         biHeight = -biHeight;
223     } else {
224         topDown = SDL_FALSE;
225     }
226
227     /* Check for read error */
228     if (SDL_strcmp(SDL_GetError(), "") != 0) {
229         was_error = SDL_TRUE;
230         goto done;
231     }
232
233     /* Expand 1 and 4 bit bitmaps to 8 bits per pixel */
234     switch (biBitCount) {
235     case 1:
236     case 4:
237         ExpandBMP = biBitCount;
238         biBitCount = 8;
239         break;
240     default:
241         ExpandBMP = 0;
242         break;
243     }
244
245     /* We don't support any BMP compression right now */
246     switch (biCompression) {
247     case BI_RGB:
248         /* If there are no masks, use the defaults */
249         SDL_assert(!haveRGBMasks);
250         SDL_assert(!haveAlphaMask);
251         /* Default values for the BMP format */
252         switch (biBitCount) {
253         case 15:
254         case 16:
255             Rmask = 0x7C00;
256             Gmask = 0x03E0;
257             Bmask = 0x001F;
258             break;
259         case 24:
260 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
261             Rmask = 0x000000FF;
262             Gmask = 0x0000FF00;
263             Bmask = 0x00FF0000;
264 #else
265             Rmask = 0x00FF0000;
266             Gmask = 0x0000FF00;
267             Bmask = 0x000000FF;
268 #endif
269             break;
270         case 32:
271             /* We don't know if this has alpha channel or not */
272             correctAlpha = SDL_TRUE;
273             Amask = 0xFF000000;
274             Rmask = 0x00FF0000;
275             Gmask = 0x0000FF00;
276             Bmask = 0x000000FF;
277             break;
278         default:
279             break;
280         }
281         break;
282
283     case BI_BITFIELDS:
284         break;  /* we handled this in the info header. */
285
286     default:
287         SDL_SetError("Compressed BMP files not supported");
288         was_error = SDL_TRUE;
289         goto done;
290     }
291
292     /* Create a compatible surface, note that the colors are RGB ordered */
293     surface =
294         SDL_CreateRGBSurface(0, biWidth, biHeight, biBitCount, Rmask, Gmask,
295                              Bmask, Amask);
296     if (surface == NULL) {
297         was_error = SDL_TRUE;
298         goto done;
299     }
300
301     /* Load the palette, if any */
302     palette = (surface->format)->palette;
303     if (palette) {
304         SDL_assert(biBitCount <= 8);
305         if (biClrUsed == 0) {
306             biClrUsed = 1 << biBitCount;
307         }
308         if ((int) biClrUsed > palette->ncolors) {
309             SDL_Color *colors;
310             int ncolors = biClrUsed;
311             colors =
312                 (SDL_Color *) SDL_realloc(palette->colors,
313                                           ncolors *
314                                           sizeof(*palette->colors));
315             if (!colors) {
316                 SDL_OutOfMemory();
317                 was_error = SDL_TRUE;
318                 goto done;
319             }
320             palette->ncolors = ncolors;
321             palette->colors = colors;
322         } else if ((int) biClrUsed < palette->ncolors) {
323             palette->ncolors = biClrUsed;
324         }
325         if (biSize == 12) {
326             for (i = 0; i < (int) biClrUsed; ++i) {
327                 SDL_RWread(src, &palette->colors[i].b, 1, 1);
328                 SDL_RWread(src, &palette->colors[i].g, 1, 1);
329                 SDL_RWread(src, &palette->colors[i].r, 1, 1);
330                 palette->colors[i].a = SDL_ALPHA_OPAQUE;
331             }
332         } else {
333             for (i = 0; i < (int) biClrUsed; ++i) {
334                 SDL_RWread(src, &palette->colors[i].b, 1, 1);
335                 SDL_RWread(src, &palette->colors[i].g, 1, 1);
336                 SDL_RWread(src, &palette->colors[i].r, 1, 1);
337                 SDL_RWread(src, &palette->colors[i].a, 1, 1);
338
339                 /* According to Microsoft documentation, the fourth element
340                    is reserved and must be zero, so we shouldn't treat it as
341                    alpha.
342                 */
343                 palette->colors[i].a = SDL_ALPHA_OPAQUE;
344             }
345         }
346     }
347
348     /* Read the surface pixels.  Note that the bmp image is upside down */
349     if (SDL_RWseek(src, fp_offset + bfOffBits, RW_SEEK_SET) < 0) {
350         SDL_Error(SDL_EFSEEK);
351         was_error = SDL_TRUE;
352         goto done;
353     }
354     top = (Uint8 *)surface->pixels;
355     end = (Uint8 *)surface->pixels+(surface->h*surface->pitch);
356     switch (ExpandBMP) {
357     case 1:
358         bmpPitch = (biWidth + 7) >> 3;
359         pad = (((bmpPitch) % 4) ? (4 - ((bmpPitch) % 4)) : 0);
360         break;
361     case 4:
362         bmpPitch = (biWidth + 1) >> 1;
363         pad = (((bmpPitch) % 4) ? (4 - ((bmpPitch) % 4)) : 0);
364         break;
365     default:
366         pad = ((surface->pitch % 4) ? (4 - (surface->pitch % 4)) : 0);
367         break;
368     }
369     if (topDown) {
370         bits = top;
371     } else {
372         bits = end - surface->pitch;
373     }
374     while (bits >= top && bits < end) {
375         switch (ExpandBMP) {
376         case 1:
377         case 4:{
378                 Uint8 pixel = 0;
379                 int shift = (8 - ExpandBMP);
380                 for (i = 0; i < surface->w; ++i) {
381                     if (i % (8 / ExpandBMP) == 0) {
382                         if (!SDL_RWread(src, &pixel, 1, 1)) {
383                             SDL_SetError("Error reading from BMP");
384                             was_error = SDL_TRUE;
385                             goto done;
386                         }
387                     }
388                     *(bits + i) = (pixel >> shift);
389                     pixel <<= ExpandBMP;
390                 }
391             }
392             break;
393
394         default:
395             if (SDL_RWread(src, bits, 1, surface->pitch)
396                 != surface->pitch) {
397                 SDL_Error(SDL_EFREAD);
398                 was_error = SDL_TRUE;
399                 goto done;
400             }
401 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
402             /* Byte-swap the pixels if needed. Note that the 24bpp
403                case has already been taken care of above. */
404             switch (biBitCount) {
405             case 15:
406             case 16:{
407                     Uint16 *pix = (Uint16 *) bits;
408                     for (i = 0; i < surface->w; i++)
409                         pix[i] = SDL_Swap16(pix[i]);
410                     break;
411                 }
412
413             case 32:{
414                     Uint32 *pix = (Uint32 *) bits;
415                     for (i = 0; i < surface->w; i++)
416                         pix[i] = SDL_Swap32(pix[i]);
417                     break;
418                 }
419             }
420 #endif
421             break;
422         }
423         /* Skip padding bytes, ugh */
424         if (pad) {
425             Uint8 padbyte;
426             for (i = 0; i < pad; ++i) {
427                 SDL_RWread(src, &padbyte, 1, 1);
428             }
429         }
430         if (topDown) {
431             bits += surface->pitch;
432         } else {
433             bits -= surface->pitch;
434         }
435     }
436     if (correctAlpha) {
437         CorrectAlphaChannel(surface);
438     }
439   done:
440     if (was_error) {
441         if (src) {
442             SDL_RWseek(src, fp_offset, RW_SEEK_SET);
443         }
444         SDL_FreeSurface(surface);
445         surface = NULL;
446     }
447     if (freesrc && src) {
448         SDL_RWclose(src);
449     }
450     return (surface);
451 }
452
453 int
454 SDL_SaveBMP_RW(SDL_Surface * saveme, SDL_RWops * dst, int freedst)
455 {
456     Sint64 fp_offset;
457     int i, pad;
458     SDL_Surface *surface;
459     Uint8 *bits;
460
461     /* The Win32 BMP file header (14 bytes) */
462     char magic[2] = { 'B', 'M' };
463     Uint32 bfSize;
464     Uint16 bfReserved1;
465     Uint16 bfReserved2;
466     Uint32 bfOffBits;
467
468     /* The Win32 BITMAPINFOHEADER struct (40 bytes) */
469     Uint32 biSize;
470     Sint32 biWidth;
471     Sint32 biHeight;
472     Uint16 biPlanes;
473     Uint16 biBitCount;
474     Uint32 biCompression;
475     Uint32 biSizeImage;
476     Sint32 biXPelsPerMeter;
477     Sint32 biYPelsPerMeter;
478     Uint32 biClrUsed;
479     Uint32 biClrImportant;
480
481     /* Make sure we have somewhere to save */
482     surface = NULL;
483     if (dst) {
484         SDL_bool save32bit = SDL_FALSE;
485 #ifdef SAVE_32BIT_BMP
486         /* We can save alpha information in a 32-bit BMP */
487         if (saveme->map->info.flags & SDL_COPY_COLORKEY ||
488             saveme->format->Amask) {
489             save32bit = SDL_TRUE;
490         }
491 #endif /* SAVE_32BIT_BMP */
492
493         if (saveme->format->palette && !save32bit) {
494             if (saveme->format->BitsPerPixel == 8) {
495                 surface = saveme;
496             } else {
497                 SDL_SetError("%d bpp BMP files not supported",
498                              saveme->format->BitsPerPixel);
499             }
500         } else if ((saveme->format->BitsPerPixel == 24) &&
501 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
502                    (saveme->format->Rmask == 0x00FF0000) &&
503                    (saveme->format->Gmask == 0x0000FF00) &&
504                    (saveme->format->Bmask == 0x000000FF)
505 #else
506                    (saveme->format->Rmask == 0x000000FF) &&
507                    (saveme->format->Gmask == 0x0000FF00) &&
508                    (saveme->format->Bmask == 0x00FF0000)
509 #endif
510             ) {
511             surface = saveme;
512         } else {
513             SDL_PixelFormat format;
514
515             /* If the surface has a colorkey or alpha channel we'll save a
516                32-bit BMP with alpha channel, otherwise save a 24-bit BMP. */
517             if (save32bit) {
518                 SDL_InitFormat(&format,
519 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
520                                SDL_PIXELFORMAT_ARGB8888
521 #else
522                                SDL_PIXELFORMAT_BGRA8888
523 #endif
524                                );
525             } else {
526                 SDL_InitFormat(&format, SDL_PIXELFORMAT_BGR24);
527             }
528             surface = SDL_ConvertSurface(saveme, &format, 0);
529             if (!surface) {
530                 SDL_SetError("Couldn't convert image to %d bpp",
531                              format.BitsPerPixel);
532             }
533         }
534     } else {
535         /* Set no error here because it may overwrite a more useful message from
536            SDL_RWFromFile() if SDL_SaveBMP_RW() is called from SDL_SaveBMP(). */
537         return -1;
538     }
539
540     if (surface && (SDL_LockSurface(surface) == 0)) {
541         const int bw = surface->w * surface->format->BytesPerPixel;
542
543         /* Set the BMP file header values */
544         bfSize = 0;             /* We'll write this when we're done */
545         bfReserved1 = 0;
546         bfReserved2 = 0;
547         bfOffBits = 0;          /* We'll write this when we're done */
548
549         /* Write the BMP file header values */
550         fp_offset = SDL_RWtell(dst);
551         SDL_ClearError();
552         SDL_RWwrite(dst, magic, 2, 1);
553         SDL_WriteLE32(dst, bfSize);
554         SDL_WriteLE16(dst, bfReserved1);
555         SDL_WriteLE16(dst, bfReserved2);
556         SDL_WriteLE32(dst, bfOffBits);
557
558         /* Set the BMP info values */
559         biSize = 40;
560         biWidth = surface->w;
561         biHeight = surface->h;
562         biPlanes = 1;
563         biBitCount = surface->format->BitsPerPixel;
564         biCompression = BI_RGB;
565         biSizeImage = surface->h * surface->pitch;
566         biXPelsPerMeter = 0;
567         biYPelsPerMeter = 0;
568         if (surface->format->palette) {
569             biClrUsed = surface->format->palette->ncolors;
570         } else {
571             biClrUsed = 0;
572         }
573         biClrImportant = 0;
574
575         /* Write the BMP info values */
576         SDL_WriteLE32(dst, biSize);
577         SDL_WriteLE32(dst, biWidth);
578         SDL_WriteLE32(dst, biHeight);
579         SDL_WriteLE16(dst, biPlanes);
580         SDL_WriteLE16(dst, biBitCount);
581         SDL_WriteLE32(dst, biCompression);
582         SDL_WriteLE32(dst, biSizeImage);
583         SDL_WriteLE32(dst, biXPelsPerMeter);
584         SDL_WriteLE32(dst, biYPelsPerMeter);
585         SDL_WriteLE32(dst, biClrUsed);
586         SDL_WriteLE32(dst, biClrImportant);
587
588         /* Write the palette (in BGR color order) */
589         if (surface->format->palette) {
590             SDL_Color *colors;
591             int ncolors;
592
593             colors = surface->format->palette->colors;
594             ncolors = surface->format->palette->ncolors;
595             for (i = 0; i < ncolors; ++i) {
596                 SDL_RWwrite(dst, &colors[i].b, 1, 1);
597                 SDL_RWwrite(dst, &colors[i].g, 1, 1);
598                 SDL_RWwrite(dst, &colors[i].r, 1, 1);
599                 SDL_RWwrite(dst, &colors[i].a, 1, 1);
600             }
601         }
602
603         /* Write the bitmap offset */
604         bfOffBits = (Uint32)(SDL_RWtell(dst) - fp_offset);
605         if (SDL_RWseek(dst, fp_offset + 10, RW_SEEK_SET) < 0) {
606             SDL_Error(SDL_EFSEEK);
607         }
608         SDL_WriteLE32(dst, bfOffBits);
609         if (SDL_RWseek(dst, fp_offset + bfOffBits, RW_SEEK_SET) < 0) {
610             SDL_Error(SDL_EFSEEK);
611         }
612
613         /* Write the bitmap image upside down */
614         bits = (Uint8 *) surface->pixels + (surface->h * surface->pitch);
615         pad = ((bw % 4) ? (4 - (bw % 4)) : 0);
616         while (bits > (Uint8 *) surface->pixels) {
617             bits -= surface->pitch;
618             if (SDL_RWwrite(dst, bits, 1, bw) != bw) {
619                 SDL_Error(SDL_EFWRITE);
620                 break;
621             }
622             if (pad) {
623                 const Uint8 padbyte = 0;
624                 for (i = 0; i < pad; ++i) {
625                     SDL_RWwrite(dst, &padbyte, 1, 1);
626                 }
627             }
628         }
629
630         /* Write the BMP file size */
631         bfSize = (Uint32)(SDL_RWtell(dst) - fp_offset);
632         if (SDL_RWseek(dst, fp_offset + 2, RW_SEEK_SET) < 0) {
633             SDL_Error(SDL_EFSEEK);
634         }
635         SDL_WriteLE32(dst, bfSize);
636         if (SDL_RWseek(dst, fp_offset + bfSize, RW_SEEK_SET) < 0) {
637             SDL_Error(SDL_EFSEEK);
638         }
639
640         /* Close it up.. */
641         SDL_UnlockSurface(surface);
642         if (surface != saveme) {
643             SDL_FreeSurface(surface);
644         }
645     }
646
647     if (freedst && dst) {
648         SDL_RWclose(dst);
649     }
650     return ((SDL_strcmp(SDL_GetError(), "") == 0) ? 0 : -1);
651 }
652
653 /* vi: set ts=4 sw=4 expandtab: */