src/video/SDL_bmp.c
author Ryan C. Gordon <icculus@icculus.org>
Wed, 09 Jul 2014 00:01:34 -0400
changeset 9000 670efcbd447d
parent 8675 7680f784d850
child 9001 c750aab87e82
permissions -rw-r--r--
Make SDL_LoadBMP() work with 32-bit bitmaps from ImageMagick.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2014 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;
    89     Uint32 Gmask;
    90     Uint32 Bmask;
    91     Uint32 Amask;
    92     SDL_Palette *palette;
    93     Uint8 *bits;
    94     Uint8 *top, *end;
    95     SDL_bool topDown;
    96     int ExpandBMP;
    97     SDL_bool correctAlpha = SDL_FALSE;
    98 
    99     /* The Win32 BMP file header (14 bytes) */
   100     char magic[2];
   101     /* Uint32 bfSize = 0; */
   102     /* Uint16 bfReserved1 = 0; */
   103     /* Uint16 bfReserved2 = 0; */
   104     Uint32 bfOffBits = 0;
   105 
   106     /* The Win32 BITMAPINFOHEADER struct (40 bytes) */
   107     Uint32 biSize = 0;
   108     Sint32 biWidth = 0;
   109     Sint32 biHeight = 0;
   110     /* Uint16 biPlanes = 0; */
   111     Uint16 biBitCount = 0;
   112     Uint32 biCompression = 0;
   113     /* Uint32 biSizeImage = 0; */
   114     /* Sint32 biXPelsPerMeter = 0; */
   115     /* Sint32 biYPelsPerMeter = 0; */
   116     Uint32 biClrUsed = 0;
   117     /* Uint32 biClrImportant = 0; */
   118 
   119     /* Make sure we are passed a valid data source */
   120     surface = NULL;
   121     was_error = SDL_FALSE;
   122     if (src == NULL) {
   123         was_error = SDL_TRUE;
   124         goto done;
   125     }
   126 
   127     /* Read in the BMP file header */
   128     fp_offset = SDL_RWtell(src);
   129     SDL_ClearError();
   130     if (SDL_RWread(src, magic, 1, 2) != 2) {
   131         SDL_Error(SDL_EFREAD);
   132         was_error = SDL_TRUE;
   133         goto done;
   134     }
   135     if (SDL_strncmp(magic, "BM", 2) != 0) {
   136         SDL_SetError("File is not a Windows BMP file");
   137         was_error = SDL_TRUE;
   138         goto done;
   139     }
   140     /* bfSize = */ SDL_ReadLE32(src);
   141     /* bfReserved1 = */ SDL_ReadLE16(src);
   142     /* bfReserved2 = */ SDL_ReadLE16(src);
   143     bfOffBits = SDL_ReadLE32(src);
   144 
   145     /* Read the Win32 BITMAPINFOHEADER */
   146     biSize = SDL_ReadLE32(src);
   147     if (biSize == 12) {
   148         biWidth = (Uint32) SDL_ReadLE16(src);
   149         biHeight = (Uint32) SDL_ReadLE16(src);
   150         /* biPlanes = */ SDL_ReadLE16(src);
   151         biBitCount = SDL_ReadLE16(src);
   152         biCompression = BI_RGB;
   153     } else {
   154         const unsigned int headerSize = 40;
   155 
   156         biWidth = SDL_ReadLE32(src);
   157         biHeight = SDL_ReadLE32(src);
   158         /* biPlanes = */ SDL_ReadLE16(src);
   159         biBitCount = SDL_ReadLE16(src);
   160         biCompression = SDL_ReadLE32(src);
   161         /* biSizeImage = */ SDL_ReadLE32(src);
   162         /* biXPelsPerMeter = */ SDL_ReadLE32(src);
   163         /* biYPelsPerMeter = */ SDL_ReadLE32(src);
   164         biClrUsed = SDL_ReadLE32(src);
   165         /* biClrImportant = */ SDL_ReadLE32(src);
   166 
   167         if (biSize > headerSize) {
   168             SDL_RWseek(src, (biSize - headerSize), RW_SEEK_CUR);
   169         }
   170     }
   171     if (biHeight < 0) {
   172         topDown = SDL_TRUE;
   173         biHeight = -biHeight;
   174     } else {
   175         topDown = SDL_FALSE;
   176     }
   177 
   178     /* Check for read error */
   179     if (SDL_strcmp(SDL_GetError(), "") != 0) {
   180         was_error = SDL_TRUE;
   181         goto done;
   182     }
   183 
   184     /* Expand 1 and 4 bit bitmaps to 8 bits per pixel */
   185     switch (biBitCount) {
   186     case 1:
   187     case 4:
   188         ExpandBMP = biBitCount;
   189         biBitCount = 8;
   190         break;
   191     default:
   192         ExpandBMP = 0;
   193         break;
   194     }
   195 
   196     /* We don't support any BMP compression right now */
   197     Rmask = Gmask = Bmask = Amask = 0;
   198     switch (biCompression) {
   199     case BI_RGB:
   200         /* If there are no masks, use the defaults */
   201         if (bfOffBits == (14 + biSize)) {
   202             /* Default values for the BMP format */
   203             switch (biBitCount) {
   204             case 15:
   205             case 16:
   206                 Rmask = 0x7C00;
   207                 Gmask = 0x03E0;
   208                 Bmask = 0x001F;
   209                 break;
   210             case 24:
   211 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
   212                 Rmask = 0x000000FF;
   213                 Gmask = 0x0000FF00;
   214                 Bmask = 0x00FF0000;
   215 #else
   216                 Rmask = 0x00FF0000;
   217                 Gmask = 0x0000FF00;
   218                 Bmask = 0x000000FF;
   219 #endif
   220                 break;
   221             case 32:
   222                 /* We don't know if this has alpha channel or not */
   223                 correctAlpha = SDL_TRUE;
   224                 Amask = 0xFF000000;
   225                 Rmask = 0x00FF0000;
   226                 Gmask = 0x0000FF00;
   227                 Bmask = 0x000000FF;
   228                 break;
   229             default:
   230                 break;
   231             }
   232             break;
   233         }
   234         /* Fall through -- read the RGB masks */
   235 
   236     case BI_BITFIELDS:
   237         switch (biBitCount) {
   238         case 15:
   239         case 16:
   240             Rmask = SDL_ReadLE32(src);
   241             Gmask = SDL_ReadLE32(src);
   242             Bmask = SDL_ReadLE32(src);
   243             break;
   244         case 32:
   245             Rmask = SDL_ReadLE32(src);
   246             Gmask = SDL_ReadLE32(src);
   247             Bmask = SDL_ReadLE32(src);
   248             Amask = SDL_ReadLE32(src);
   249 
   250             /* ImageMagick seems to put out bogus masks here. Pick a default. */
   251             if ((Rmask == 0xFFFFFF) && (Gmask == 0xFFFFFF) &&
   252                 (Bmask == 0xFFFFFF) && (Amask == 0xFFFFFF) ) {
   253                 Amask = 0xFF000000;
   254                 Rmask = 0x00FF0000;
   255                 Gmask = 0x0000FF00;
   256                 Bmask = 0x000000FF;
   257             }
   258 
   259             break;
   260         default:
   261             break;
   262         }
   263         break;
   264     default:
   265         SDL_SetError("Compressed BMP files not supported");
   266         was_error = SDL_TRUE;
   267         goto done;
   268     }
   269 
   270     /* Create a compatible surface, note that the colors are RGB ordered */
   271     surface =
   272         SDL_CreateRGBSurface(0, biWidth, biHeight, biBitCount, Rmask, Gmask,
   273                              Bmask, Amask);
   274     if (surface == NULL) {
   275         was_error = SDL_TRUE;
   276         goto done;
   277     }
   278 
   279     /* Load the palette, if any */
   280     palette = (surface->format)->palette;
   281     if (palette) {
   282         SDL_assert(biBitCount <= 8);
   283         if (biClrUsed == 0) {
   284             biClrUsed = 1 << biBitCount;
   285         }
   286         if ((int) biClrUsed > palette->ncolors) {
   287             palette->ncolors = biClrUsed;
   288             palette->colors =
   289                 (SDL_Color *) SDL_realloc(palette->colors,
   290                                           palette->ncolors *
   291                                           sizeof(*palette->colors));
   292             if (!palette->colors) {
   293                 SDL_OutOfMemory();
   294                 was_error = SDL_TRUE;
   295                 goto done;
   296             }
   297         } else if ((int) biClrUsed < palette->ncolors) {
   298             palette->ncolors = biClrUsed;
   299         }
   300         if (biSize == 12) {
   301             for (i = 0; i < (int) biClrUsed; ++i) {
   302                 SDL_RWread(src, &palette->colors[i].b, 1, 1);
   303                 SDL_RWread(src, &palette->colors[i].g, 1, 1);
   304                 SDL_RWread(src, &palette->colors[i].r, 1, 1);
   305                 palette->colors[i].a = SDL_ALPHA_OPAQUE;
   306             }
   307         } else {
   308             for (i = 0; i < (int) biClrUsed; ++i) {
   309                 SDL_RWread(src, &palette->colors[i].b, 1, 1);
   310                 SDL_RWread(src, &palette->colors[i].g, 1, 1);
   311                 SDL_RWread(src, &palette->colors[i].r, 1, 1);
   312                 SDL_RWread(src, &palette->colors[i].a, 1, 1);
   313 
   314                 /* According to Microsoft documentation, the fourth element
   315                    is reserved and must be zero, so we shouldn't treat it as
   316                    alpha.
   317                 */
   318                 palette->colors[i].a = SDL_ALPHA_OPAQUE;
   319             }
   320         }
   321     }
   322 
   323     /* Read the surface pixels.  Note that the bmp image is upside down */
   324     if (SDL_RWseek(src, fp_offset + bfOffBits, RW_SEEK_SET) < 0) {
   325         SDL_Error(SDL_EFSEEK);
   326         was_error = SDL_TRUE;
   327         goto done;
   328     }
   329     top = (Uint8 *)surface->pixels;
   330     end = (Uint8 *)surface->pixels+(surface->h*surface->pitch);
   331     switch (ExpandBMP) {
   332     case 1:
   333         bmpPitch = (biWidth + 7) >> 3;
   334         pad = (((bmpPitch) % 4) ? (4 - ((bmpPitch) % 4)) : 0);
   335         break;
   336     case 4:
   337         bmpPitch = (biWidth + 1) >> 1;
   338         pad = (((bmpPitch) % 4) ? (4 - ((bmpPitch) % 4)) : 0);
   339         break;
   340     default:
   341         pad = ((surface->pitch % 4) ? (4 - (surface->pitch % 4)) : 0);
   342         break;
   343     }
   344     if (topDown) {
   345         bits = top;
   346     } else {
   347         bits = end - surface->pitch;
   348     }
   349     while (bits >= top && bits < end) {
   350         switch (ExpandBMP) {
   351         case 1:
   352         case 4:{
   353                 Uint8 pixel = 0;
   354                 int shift = (8 - ExpandBMP);
   355                 for (i = 0; i < surface->w; ++i) {
   356                     if (i % (8 / ExpandBMP) == 0) {
   357                         if (!SDL_RWread(src, &pixel, 1, 1)) {
   358                             SDL_SetError("Error reading from BMP");
   359                             was_error = SDL_TRUE;
   360                             goto done;
   361                         }
   362                     }
   363                     *(bits + i) = (pixel >> shift);
   364                     pixel <<= ExpandBMP;
   365                 }
   366             }
   367             break;
   368 
   369         default:
   370             if (SDL_RWread(src, bits, 1, surface->pitch)
   371                 != surface->pitch) {
   372                 SDL_Error(SDL_EFREAD);
   373                 was_error = SDL_TRUE;
   374                 goto done;
   375             }
   376 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
   377             /* Byte-swap the pixels if needed. Note that the 24bpp
   378                case has already been taken care of above. */
   379             switch (biBitCount) {
   380             case 15:
   381             case 16:{
   382                     Uint16 *pix = (Uint16 *) bits;
   383                     for (i = 0; i < surface->w; i++)
   384                         pix[i] = SDL_Swap16(pix[i]);
   385                     break;
   386                 }
   387 
   388             case 32:{
   389                     Uint32 *pix = (Uint32 *) bits;
   390                     for (i = 0; i < surface->w; i++)
   391                         pix[i] = SDL_Swap32(pix[i]);
   392                     break;
   393                 }
   394             }
   395 #endif
   396             break;
   397         }
   398         /* Skip padding bytes, ugh */
   399         if (pad) {
   400             Uint8 padbyte;
   401             for (i = 0; i < pad; ++i) {
   402                 SDL_RWread(src, &padbyte, 1, 1);
   403             }
   404         }
   405         if (topDown) {
   406             bits += surface->pitch;
   407         } else {
   408             bits -= surface->pitch;
   409         }
   410     }
   411     if (correctAlpha) {
   412         CorrectAlphaChannel(surface);
   413     }
   414   done:
   415     if (was_error) {
   416         if (src) {
   417             SDL_RWseek(src, fp_offset, RW_SEEK_SET);
   418         }
   419         SDL_FreeSurface(surface);
   420         surface = NULL;
   421     }
   422     if (freesrc && src) {
   423         SDL_RWclose(src);
   424     }
   425     return (surface);
   426 }
   427 
   428 int
   429 SDL_SaveBMP_RW(SDL_Surface * saveme, SDL_RWops * dst, int freedst)
   430 {
   431     Sint64 fp_offset;
   432     int i, pad;
   433     SDL_Surface *surface;
   434     Uint8 *bits;
   435 
   436     /* The Win32 BMP file header (14 bytes) */
   437     char magic[2] = { 'B', 'M' };
   438     Uint32 bfSize;
   439     Uint16 bfReserved1;
   440     Uint16 bfReserved2;
   441     Uint32 bfOffBits;
   442 
   443     /* The Win32 BITMAPINFOHEADER struct (40 bytes) */
   444     Uint32 biSize;
   445     Sint32 biWidth;
   446     Sint32 biHeight;
   447     Uint16 biPlanes;
   448     Uint16 biBitCount;
   449     Uint32 biCompression;
   450     Uint32 biSizeImage;
   451     Sint32 biXPelsPerMeter;
   452     Sint32 biYPelsPerMeter;
   453     Uint32 biClrUsed;
   454     Uint32 biClrImportant;
   455 
   456     /* Make sure we have somewhere to save */
   457     surface = NULL;
   458     if (dst) {
   459         SDL_bool save32bit = SDL_FALSE;
   460 #ifdef SAVE_32BIT_BMP
   461         /* We can save alpha information in a 32-bit BMP */
   462         if (saveme->map->info.flags & SDL_COPY_COLORKEY ||
   463             saveme->format->Amask) {
   464             save32bit = SDL_TRUE;
   465         }
   466 #endif /* SAVE_32BIT_BMP */
   467 
   468         if (saveme->format->palette && !save32bit) {
   469             if (saveme->format->BitsPerPixel == 8) {
   470                 surface = saveme;
   471             } else {
   472                 SDL_SetError("%d bpp BMP files not supported",
   473                              saveme->format->BitsPerPixel);
   474             }
   475         } else if ((saveme->format->BitsPerPixel == 24) &&
   476 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
   477                    (saveme->format->Rmask == 0x00FF0000) &&
   478                    (saveme->format->Gmask == 0x0000FF00) &&
   479                    (saveme->format->Bmask == 0x000000FF)
   480 #else
   481                    (saveme->format->Rmask == 0x000000FF) &&
   482                    (saveme->format->Gmask == 0x0000FF00) &&
   483                    (saveme->format->Bmask == 0x00FF0000)
   484 #endif
   485             ) {
   486             surface = saveme;
   487         } else {
   488             SDL_PixelFormat format;
   489 
   490             /* If the surface has a colorkey or alpha channel we'll save a
   491                32-bit BMP with alpha channel, otherwise save a 24-bit BMP. */
   492             if (save32bit) {
   493                 SDL_InitFormat(&format,
   494 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
   495                                SDL_PIXELFORMAT_ARGB8888
   496 #else
   497                                SDL_PIXELFORMAT_BGRA8888
   498 #endif
   499                                );
   500             } else {
   501                 SDL_InitFormat(&format, SDL_PIXELFORMAT_BGR24);
   502             }
   503             surface = SDL_ConvertSurface(saveme, &format, 0);
   504             if (!surface) {
   505                 SDL_SetError("Couldn't convert image to %d bpp",
   506                              format.BitsPerPixel);
   507             }
   508         }
   509     }
   510 
   511     if (surface && (SDL_LockSurface(surface) == 0)) {
   512         const int bw = surface->w * surface->format->BytesPerPixel;
   513 
   514         /* Set the BMP file header values */
   515         bfSize = 0;             /* We'll write this when we're done */
   516         bfReserved1 = 0;
   517         bfReserved2 = 0;
   518         bfOffBits = 0;          /* We'll write this when we're done */
   519 
   520         /* Write the BMP file header values */
   521         fp_offset = SDL_RWtell(dst);
   522         SDL_ClearError();
   523         SDL_RWwrite(dst, magic, 2, 1);
   524         SDL_WriteLE32(dst, bfSize);
   525         SDL_WriteLE16(dst, bfReserved1);
   526         SDL_WriteLE16(dst, bfReserved2);
   527         SDL_WriteLE32(dst, bfOffBits);
   528 
   529         /* Set the BMP info values */
   530         biSize = 40;
   531         biWidth = surface->w;
   532         biHeight = surface->h;
   533         biPlanes = 1;
   534         biBitCount = surface->format->BitsPerPixel;
   535         biCompression = BI_RGB;
   536         biSizeImage = surface->h * surface->pitch;
   537         biXPelsPerMeter = 0;
   538         biYPelsPerMeter = 0;
   539         if (surface->format->palette) {
   540             biClrUsed = surface->format->palette->ncolors;
   541         } else {
   542             biClrUsed = 0;
   543         }
   544         biClrImportant = 0;
   545 
   546         /* Write the BMP info values */
   547         SDL_WriteLE32(dst, biSize);
   548         SDL_WriteLE32(dst, biWidth);
   549         SDL_WriteLE32(dst, biHeight);
   550         SDL_WriteLE16(dst, biPlanes);
   551         SDL_WriteLE16(dst, biBitCount);
   552         SDL_WriteLE32(dst, biCompression);
   553         SDL_WriteLE32(dst, biSizeImage);
   554         SDL_WriteLE32(dst, biXPelsPerMeter);
   555         SDL_WriteLE32(dst, biYPelsPerMeter);
   556         SDL_WriteLE32(dst, biClrUsed);
   557         SDL_WriteLE32(dst, biClrImportant);
   558 
   559         /* Write the palette (in BGR color order) */
   560         if (surface->format->palette) {
   561             SDL_Color *colors;
   562             int ncolors;
   563 
   564             colors = surface->format->palette->colors;
   565             ncolors = surface->format->palette->ncolors;
   566             for (i = 0; i < ncolors; ++i) {
   567                 SDL_RWwrite(dst, &colors[i].b, 1, 1);
   568                 SDL_RWwrite(dst, &colors[i].g, 1, 1);
   569                 SDL_RWwrite(dst, &colors[i].r, 1, 1);
   570                 SDL_RWwrite(dst, &colors[i].a, 1, 1);
   571             }
   572         }
   573 
   574         /* Write the bitmap offset */
   575         bfOffBits = (Uint32)(SDL_RWtell(dst) - fp_offset);
   576         if (SDL_RWseek(dst, fp_offset + 10, RW_SEEK_SET) < 0) {
   577             SDL_Error(SDL_EFSEEK);
   578         }
   579         SDL_WriteLE32(dst, bfOffBits);
   580         if (SDL_RWseek(dst, fp_offset + bfOffBits, RW_SEEK_SET) < 0) {
   581             SDL_Error(SDL_EFSEEK);
   582         }
   583 
   584         /* Write the bitmap image upside down */
   585         bits = (Uint8 *) surface->pixels + (surface->h * surface->pitch);
   586         pad = ((bw % 4) ? (4 - (bw % 4)) : 0);
   587         while (bits > (Uint8 *) surface->pixels) {
   588             bits -= surface->pitch;
   589             if (SDL_RWwrite(dst, bits, 1, bw) != bw) {
   590                 SDL_Error(SDL_EFWRITE);
   591                 break;
   592             }
   593             if (pad) {
   594                 const Uint8 padbyte = 0;
   595                 for (i = 0; i < pad; ++i) {
   596                     SDL_RWwrite(dst, &padbyte, 1, 1);
   597                 }
   598             }
   599         }
   600 
   601         /* Write the BMP file size */
   602         bfSize = (Uint32)(SDL_RWtell(dst) - fp_offset);
   603         if (SDL_RWseek(dst, fp_offset + 2, RW_SEEK_SET) < 0) {
   604             SDL_Error(SDL_EFSEEK);
   605         }
   606         SDL_WriteLE32(dst, bfSize);
   607         if (SDL_RWseek(dst, fp_offset + bfSize, RW_SEEK_SET) < 0) {
   608             SDL_Error(SDL_EFSEEK);
   609         }
   610 
   611         /* Close it up.. */
   612         SDL_UnlockSurface(surface);
   613         if (surface != saveme) {
   614             SDL_FreeSurface(surface);
   615         }
   616     }
   617 
   618     if (freedst && dst) {
   619         SDL_RWclose(dst);
   620     }
   621     return ((SDL_strcmp(SDL_GetError(), "") == 0) ? 0 : -1);
   622 }
   623 
   624 /* vi: set ts=4 sw=4 expandtab: */