1 /***************************************************************************
2  *   Free Heroes of Might and Magic II: https://github.com/ihhub/fheroes2  *
3  *   Copyright (C) 2020                                                    *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  *   This program 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         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20 
21 #include "image_tool.h"
22 #include "image_palette.h"
23 
24 #include <SDL_version.h>
25 #if SDL_VERSION_ATLEAST( 2, 0, 0 )
26 #include <SDL_surface.h>
27 #else
28 #include <SDL_video.h>
29 #endif
30 
31 #if defined( FHEROES2_IMAGE_SUPPORT )
32 #if SDL_VERSION_ATLEAST( 2, 0, 0 )
33 #define FHEROES2_ENABLE_PNG 1
34 #include <SDL_image.h>
35 #endif
36 #endif
37 
38 namespace
39 {
PALPalette()40     std::vector<uint8_t> PALPalette()
41     {
42         const uint8_t * gamePalette = fheroes2::getGamePalette();
43 
44         std::vector<uint8_t> palette( 256 * 3 );
45         for ( size_t i = 0; i < palette.size(); ++i ) {
46             palette[i] = gamePalette[i] << 2;
47         }
48 
49         return palette;
50     }
51 
SaveImage(const fheroes2::Image & image,const std::string & path)52     bool SaveImage( const fheroes2::Image & image, const std::string & path )
53     {
54         const std::vector<uint8_t> & palette = PALPalette();
55         const uint8_t * currentPalette = palette.data();
56 
57 #if SDL_VERSION_ATLEAST( 2, 0, 0 )
58         SDL_Surface * surface = SDL_CreateRGBSurface( 0, image.width(), image.height(), 32, 0xFF, 0xFF00, 0xFF0000, 0xFF000000 );
59 #else
60         SDL_Surface * surface = SDL_CreateRGBSurface( SDL_SWSURFACE, image.width(), image.height(), 32, 0xFF, 0xFF00, 0xFF0000, 0xFF000000 );
61 #endif
62         if ( surface == nullptr )
63             return false;
64 
65         const uint32_t width = image.width();
66         const uint32_t height = image.height();
67 
68         uint32_t * out = static_cast<uint32_t *>( surface->pixels );
69         const uint32_t * outEnd = out + width * height;
70         const uint8_t * in = image.image();
71 
72         if ( surface->format->Amask > 0 ) {
73             const uint8_t * transform = image.transform();
74 
75             for ( ; out != outEnd; ++out, ++in, ++transform ) {
76                 if ( *transform == 1 ) {
77                     *out = SDL_MapRGBA( surface->format, 0, 0, 0, 0 );
78                 }
79                 else if ( *transform == 2 ) {
80                     *out = SDL_MapRGBA( surface->format, 0, 0, 0, 64 );
81                 }
82                 else {
83                     const uint8_t * value = currentPalette + *in * 3;
84                     *out = SDL_MapRGBA( surface->format, *value, *( value + 1 ), *( value + 2 ), 255 );
85                 }
86             }
87         }
88         else {
89             for ( ; out != outEnd; ++out, ++in ) {
90                 const uint8_t * value = currentPalette + *in * 3;
91                 *out = SDL_MapRGB( surface->format, *value, *( value + 1 ), *( value + 2 ) );
92             }
93         }
94 
95 #if defined( FHEROES2_ENABLE_PNG )
96         int res = 0;
97         const std::string pngExtension( ".png" );
98         if ( path.size() > pngExtension.size() && path.compare( path.size() - pngExtension.size(), pngExtension.size(), pngExtension ) == 0 ) {
99             res = IMG_SavePNG( surface, path.c_str() );
100         }
101         else {
102             res = SDL_SaveBMP( surface, path.c_str() );
103         }
104 #else
105         const int res = SDL_SaveBMP( surface, path.c_str() );
106 #endif
107 
108         SDL_FreeSurface( surface );
109 
110         return res == 0;
111     }
112 }
113 
114 namespace fheroes2
115 {
Save(const Image & image,const std::string & path,const uint8_t background)116     bool Save( const Image & image, const std::string & path, const uint8_t background )
117     {
118         if ( image.empty() || path.empty() )
119             return false;
120 
121         Image temp( image.width(), image.height() );
122         temp.fill( background );
123 
124         Blit( image, temp );
125 
126         return SaveImage( temp, path );
127     }
128 
Save(const Image & image,const std::string & path)129     bool Save( const Image & image, const std::string & path )
130     {
131         if ( image.empty() || path.empty() )
132             return false;
133 
134         return SaveImage( image, path );
135     }
136 
Load(const std::string & path,Image & image)137     bool Load( const std::string & path, Image & image )
138     {
139         SDL_Surface * surface = SDL_LoadBMP( path.c_str() );
140         if ( surface == nullptr ) {
141             return false;
142         }
143 
144         if ( surface->format->BytesPerPixel == 3 ) {
145             image.resize( surface->w, surface->h );
146             memset( image.transform(), 0, surface->w * surface->h );
147 
148             const uint8_t * inY = reinterpret_cast<uint8_t *>( surface->pixels );
149             uint8_t * outY = image.image();
150 
151             const uint8_t * inYEnd = inY + surface->h * surface->pitch;
152 
153             for ( ; inY != inYEnd; inY += surface->pitch, outY += surface->w ) {
154                 const uint8_t * inX = inY;
155                 uint8_t * outX = outY;
156                 const uint8_t * inXEnd = inX + surface->w * 3;
157 
158                 for ( ; inX != inXEnd; inX += 3, ++outX ) {
159                     *outX = GetColorId( *( inX + 2 ), *( inX + 1 ), *inX );
160                 }
161             }
162         }
163         else if ( surface->format->BytesPerPixel == 4 ) {
164             image.resize( surface->w, surface->h );
165             image.reset();
166 
167             const uint8_t * inY = reinterpret_cast<uint8_t *>( surface->pixels );
168             uint8_t * outY = image.image();
169             uint8_t * transformY = image.transform();
170 
171             const uint8_t * inYEnd = inY + surface->h * surface->pitch;
172 
173             for ( ; inY != inYEnd; inY += surface->pitch, outY += surface->w, transformY += surface->w ) {
174                 const uint8_t * inX = inY;
175                 uint8_t * outX = outY;
176                 uint8_t * transformX = transformY;
177                 const uint8_t * inXEnd = inX + surface->w * 4;
178 
179                 for ( ; inX != inXEnd; inX += 4, ++outX, ++transformX ) {
180                     const uint8_t alpha = *( inX + 3 );
181                     if ( alpha < 255 ) {
182                         if ( alpha == 0 ) {
183                             *transformX = 1;
184                         }
185                         else if ( *( inX ) == 0 && *( inX + 1 ) == 0 && *( inX + 2 ) == 0 ) {
186                             *transformX = 2;
187                         }
188                         else {
189                             *outX = GetColorId( *( inX + 2 ), *( inX + 1 ), *inX );
190                             *transformX = 0;
191                         }
192                     }
193                     else {
194                         *outX = GetColorId( *( inX + 2 ), *( inX + 1 ), *inX );
195                         *transformX = 0;
196                     }
197                 }
198             }
199         }
200         else {
201             SDL_FreeSurface( surface );
202             return false;
203         }
204 
205         SDL_FreeSurface( surface );
206 
207         return true;
208     }
209 
decodeICNSprite(const uint8_t * data,uint32_t sizeData,const int32_t width,const int32_t height,const int16_t offsetX,const int16_t offsetY)210     Sprite decodeICNSprite( const uint8_t * data, uint32_t sizeData, const int32_t width, const int32_t height, const int16_t offsetX, const int16_t offsetY )
211     {
212         Sprite sprite( width, height, offsetX, offsetY );
213         sprite.reset();
214 
215         uint8_t * imageData = sprite.image();
216         uint8_t * imageTransform = sprite.transform();
217 
218         uint32_t posX = 0;
219 
220         const uint8_t * dataEnd = data + sizeData;
221 
222         while ( true ) {
223             if ( 0 == *data ) { // 0x00 - end of row
224                 imageData += width;
225                 imageTransform += width;
226                 posX = 0;
227                 ++data;
228             }
229             else if ( 0x80 > *data ) { // 0x01-0x7F - repeat a pixel N times
230                 uint32_t pixelCount = *data;
231                 ++data;
232                 while ( pixelCount > 0 && data != dataEnd ) {
233                     imageData[posX] = *data;
234                     imageTransform[posX] = 0;
235                     ++posX;
236                     ++data;
237                     --pixelCount;
238                 }
239             }
240             else if ( 0x80 == *data ) { // 0x80 - end of image
241                 break;
242             }
243             else if ( 0xC0 > *data ) { // 0xBF - empty (transparent) pixels
244                 posX += *data - 0x80;
245                 ++data;
246             }
247             else if ( 0xC0 == *data ) { // 0xC0 - transform layer
248                 ++data;
249 
250                 const uint8_t transformValue = *data;
251                 const uint8_t transformType = static_cast<uint8_t>( ( ( transformValue & 0x3C ) << 6 ) / 256 + 2 ); // 1 is for skipping
252 
253                 uint32_t pixelCount = *data % 4 ? *data % 4 : *( ++data );
254 
255                 if ( ( transformValue & 0x40 ) && ( transformType <= 15 ) ) {
256                     while ( pixelCount > 0 ) {
257                         imageTransform[posX] = transformType;
258                         ++posX;
259                         --pixelCount;
260                     }
261                 }
262                 else {
263                     posX += pixelCount;
264                 }
265 
266                 ++data;
267             }
268             else if ( 0xC1 == *data ) { // 0xC1
269                 ++data;
270                 uint32_t pixelCount = *data;
271                 ++data;
272                 while ( pixelCount > 0 ) {
273                     imageData[posX] = *data;
274                     imageTransform[posX] = 0;
275                     ++posX;
276                     --pixelCount;
277                 }
278                 ++data;
279             }
280             else {
281                 uint32_t pixelCount = *data - 0xC0;
282                 ++data;
283                 while ( pixelCount > 0 ) {
284                     imageData[posX] = *data;
285                     imageTransform[posX] = 0;
286                     ++posX;
287                     --pixelCount;
288                 }
289                 ++data;
290             }
291 
292             if ( data >= dataEnd ) {
293                 break;
294             }
295         }
296 
297         return sprite;
298     }
299 }
300