1 // Written in the D programming language.
2 
3 /**
4 DLANGUI library.
5 
6 This module contains image loading functions.
7 
8 Currently uses FreeImage.
9 
10 Usage of libpng is not feasible under linux due to conflicts of library and binding versions.
11 
12 Synopsis:
13 
14 ----
15 import dlangui.graphics.images;
16 
17 ----
18 
19 Copyright: Vadim Lopatin, 2014
20 License:   $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
21 Authors:   $(WEB coolreader.org, Vadim Lopatin)
22 */
23 module dlangui.graphics.images;
24 
25 immutable bool USE_LIBPNG = false;
26 immutable bool USE_FREEIMAGE = true;
27 
28 import dlangui.core.logger;
29 import dlangui.core.types;
30 import dlangui.graphics.drawbuf;
31 import std.stream;
32 static if (USE_LIBPNG) {
33 	import libpng.png;
34 }
35 
36 /// load and decode image from file to ColorDrawBuf, returns null if loading or decoding is failed
37 ColorDrawBuf loadImage(string filename) {
38     Log.d("Loading image from file " ~ filename);
39     try {
40         std.stream.File f = new std.stream.File(filename);
41 	    scope(exit) { f.close(); }
42         return loadImage(f);
43     } catch (Exception e) {
44         Log.e("exception while loading image from file ", filename);
45         return null;
46     }
47 }
48 
49 /// load and decode image from stream to ColorDrawBuf, returns null if loading or decoding is failed
50 ColorDrawBuf loadImage(InputStream stream) {
51     if (stream is null || !stream.isOpen)
52         return null;
53 	static if (USE_FREEIMAGE) {
54 		return loadFreeImage(stream);
55 	} else static if (USE_LIBPNG) {
56 	    return loadPngImage(stream);
57 	}
58 }
59 
60 class ImageDecodingException : Exception {
61     this(string msg) {
62         super(msg);
63     }
64 }
65 
66 shared static this() {
67 	//import derelict.freeimage.freeimage;
68 	//DerelictFI.load();
69 }
70 
71 static if (USE_FREEIMAGE) {
72 	ColorDrawBuf loadFreeImage(InputStream stream) {
73 		import derelict.freeimage.freeimage;
74 
75 		static bool FREE_IMAGE_LOADED;
76 		if (!FREE_IMAGE_LOADED) {
77 			DerelictFI.load();
78 			FREE_IMAGE_LOADED = true;
79 		}
80 
81 		ubyte imagebuf[];
82 		ubyte readbuf[4096];
83 		for (;;) {
84 			size_t bytesRead = stream.read(readbuf);
85 			if (!bytesRead)
86 				break;
87 			imagebuf ~= readbuf[0..bytesRead];
88 		}
89 		//pointer to the image, once loaded
90 		FIBITMAP *dib = null;		//image format
91 		FREE_IMAGE_FORMAT fif = FIF_UNKNOWN;
92 		// attach the binary data to a memory stream
93 		FIMEMORY *hmem = FreeImage_OpenMemory(imagebuf.ptr, imagebuf.length);
94 		fif = FreeImage_GetFileTypeFromMemory(hmem);
95 		//check that the plugin has reading capabilities and load the file
96 		if(!FreeImage_FIFSupportsReading(fif)) {
97 			FreeImage_CloseMemory(hmem);
98 			return null;
99 		}
100 
101 		// load an image from the memory stream
102 		dib = FreeImage_LoadFromMemory(fif, hmem, 0);
103 		
104 		//if the image failed to load, return failure
105 		if (!dib) {
106 			Log.e("Failed to decode image");
107 			FreeImage_CloseMemory(hmem);
108 			return null;
109 		}
110 		//retrieve the image data
111 		ubyte * data = cast(ubyte*)FreeImage_GetBits(dib);
112 		//get the image width and height, and size per pixel
113 		int width = FreeImage_GetWidth(dib);
114 		int height = FreeImage_GetHeight(dib);
115 		int bpp = FreeImage_GetBPP(dib);
116 		int pixelSize = (bpp + 7)/8;
117 		FREE_IMAGE_COLOR_TYPE colorType = FreeImage_GetColorType(dib);
118         int transparentIndex = 0;
119         int transparencyCount = 0;
120         RGBQUAD * palette = null;
121         ubyte * transparencyTable = null;
122         if (colorType == FIC_PALETTE) {
123             palette = FreeImage_GetPalette(dib);
124             transparentIndex = FreeImage_GetTransparentIndex(dib);
125             transparencyCount = FreeImage_GetTransparencyCount(dib);
126             transparencyTable = FreeImage_GetTransparencyTable(dib);
127         }
128 		int size = width*height*pixelSize;
129 
130 		ColorDrawBuf res = new ColorDrawBuf(width, height);
131 
132 		//swap R and B and invert image while copying
133 		ubyte* src;
134 		uint* dst;
135 		uint r, g, b, a;
136 		for( int i = 0, ii = height-1; i < height ; ++i, --ii ) {
137 			dst = res.scanLine(i);
138 			src = data + (ii * width) * pixelSize;
139 			for( int j = 0; j < width; ++j, ++dst, src += pixelSize ) {
140                 if (colorType == FIC_PALETTE) {
141                     ubyte index = src[0];
142                     a = 0;
143                     if (transparencyTable !is null) {
144                         a = transparencyTable[index] ^ 0xFF;
145                     } else if (transparentIndex >= 0 && index >= transparentIndex && index < transparentIndex + transparencyCount) {
146                         a = 0xFF;
147                     }
148                     RGBQUAD pcolor = palette[index];
149                     r = pcolor.rgbRed;
150                     g = pcolor.rgbGreen;
151                     b = pcolor.rgbBlue;
152                     dst[0] = (a << 24) | (r << 16) | (g << 8) | b;
153                 } else {
154 				    a = 0;
155 				    switch (pixelSize) {
156 				    case 4:
157 					    a = src[3] ^ 255;
158 					    // fall through
159 					    goto case;
160 				    case 3:
161 					    r = src[2];
162 					    g = src[1];
163 					    b = src[0];
164 					    break;
165 				    case 2:
166 					    // todo: do something better
167 					    r = g = src[1];
168 					    b = src[0];
169 					    break;
170 				    default:
171 				    case 1:
172 				        r = g = b = src[0];
173 					    break;
174 				    }
175 				    dst[0] = (a << 24) | (r << 16) | (g << 8) | b;
176                 }
177 			}
178 		}
179 		FreeImage_CloseMemory(hmem);
180 		return res;
181 	}
182 }
183 
184 static if (USE_LIBPNG) {
185 
186 	extern (C) void lvpng_error_func (png_structp png, png_const_charp msg)
187 	{
188 		string s = fromStringz(msg);
189 		Log.d("Error while reading PNG image: ", s);
190 		// todo: exceptions do not work inside C function
191 		throw new ImageDecodingException("Error while decoding PNG image");
192 	}
193 
194 	extern (C) void lvpng_warning_func (png_structp png, png_const_charp msg)
195 	{
196 		string s = fromStringz(msg);
197 		Log.d("Warn while reading PNG image: ", s);
198 		// todo: exceptions do not work inside C function
199 		throw new ImageDecodingException("Error while decoding PNG image");
200 	}
201 
202 	extern (C) void lvpng_read_func(png_structp png, png_bytep buf, png_size_t len)
203 	{
204 		InputStream stream = cast(InputStream)png_get_io_ptr(png);
205 		ubyte[] localbuf = new ubyte[len];
206 		if (stream.read(localbuf) != len)
207 			throw new ImageDecodingException("Error while reading PNG image");
208 		for (uint i = 0; i < len; i++)
209 			buf[i] = localbuf[i];
210 	}
211 
212 	/// load and decode PNG image
213 	ColorDrawBuf loadPngImage(InputStream stream)
214 	{
215 		png_structp png_ptr = null;
216 		png_infop info_ptr = null;
217 		png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
218 										 cast(png_voidp)stream, &lvpng_error_func, &lvpng_warning_func);
219 		if ( !png_ptr )
220 			return null;
221 
222 		try {
223 			//
224 			info_ptr = png_create_info_struct(png_ptr);
225 			if (!info_ptr)
226 				lvpng_error_func(png_ptr, "cannot create png info struct");
227 			png_set_read_fn(png_ptr,
228 							cast(void*)stream, &lvpng_read_func);
229 			png_read_info( png_ptr, info_ptr );
230 
231 
232 			png_uint_32 width, height;
233 			int bit_depth, color_type, interlace_type;
234 			png_get_IHDR(png_ptr, info_ptr, &width, &height,
235 						 &bit_depth, &color_type, &interlace_type,
236 						 null, null);
237 			ColorDrawBuf drawbuf = new ColorDrawBuf(width, height);
238 
239 			if (color_type & PNG_COLOR_MASK_PALETTE)
240 				png_set_palette_to_rgb(png_ptr);
241 
242 			if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
243 				png_set_expand_gray_1_2_4_to_8(png_ptr);
244 
245 			if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
246 				png_set_tRNS_to_alpha(png_ptr);
247 
248 			if (bit_depth == 16)
249 				png_set_strip_16(png_ptr);
250 
251 			png_set_invert_alpha(png_ptr);
252 
253 			if (bit_depth < 8)
254 				png_set_packing(png_ptr);
255 
256 			png_set_filler(png_ptr, 0, PNG_FILLER_AFTER);
257 
258 			if (color_type == PNG_COLOR_TYPE_GRAY ||
259 				color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
260 				png_set_gray_to_rgb(png_ptr);
261 
262 			int number_passes = png_set_interlace_handling(png_ptr);
263 			png_set_bgr(png_ptr);
264 
265 			for (int pass = 0; pass < number_passes; pass++)
266 			{
267 				for (int y = 0; y < height; y++)
268 				{
269 					uint * row = drawbuf.scanLine(y);
270 					png_read_rows(png_ptr, cast(ubyte **)&row, null, 1);
271 				}
272 			}
273 
274 			png_read_end(png_ptr, info_ptr);
275 
276 			png_destroy_read_struct(&png_ptr, &info_ptr, null);
277 
278 			return drawbuf;
279 		} catch (ImageDecodingException e) {
280 			if (png_ptr)
281 			{
282 				png_destroy_read_struct(&png_ptr, &info_ptr, null);
283 			}
284 			return null;
285 		}
286 	}
287 
288 	//bool LVPngImageSource::CheckPattern( const lUInt8 * buf, int )
289 	//{
290 		//return( !png_sig_cmp((unsigned char *)buf, (png_size_t)0, 4) );
291 	//}
292 
293 }