Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
FITexLib.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Collections.Generic;
5 using System.Runtime.InteropServices;
6 using System.IO;
7 
8 using SiliconStudio.Core;
9 using SiliconStudio.Core.Diagnostics;
10 using SiliconStudio.TextureConverter.Requests;
11 using FreeImageAPI;
12 using FreeImageAPI.Plugins;
13 
14 namespace SiliconStudio.TextureConverter.TexLibraries
15 {
16  /// <summary>
17  /// Class containing the needed native Data used by FreeImage
18  /// </summary>
19  internal class FreeImageTextureLibraryData : ITextureLibraryData
20  {
21  /// <summary>
22  /// Array of <see cref="FIBITMAP" />, each one being a sub image of the texture (a mipmap in a specific array member)
23  /// </summary>
24  public FIBITMAP[] Bitmaps { get; set; }
25 
26  /// <summary>
27  /// Pointer to the beginning of the texture data (used for allocation/deallocation)
28  /// </summary>
29  public IntPtr Data { get; set; }
30  }
31 
32  /// <summary>
33  /// Peforms requests from <see cref="TextureTool" /> using FreeImage library.
34  /// </summary>
35  internal class FITexLib : ITexLibrary
36  {
37  private static Logger Log = GlobalLogger.GetLogger("FITexLib");
38 
39  /// <summary>
40  /// Initializes a new instance of the <see cref="FITexLib"/> class.
41  /// </summary>
42  public FITexLib() {}
43 
44  /// <summary>
45  /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. Nothing in this case.
46  /// </summary>
47  public void Dispose() {}
48 
49  public void Dispose(TexImage image)
50  {
51  FreeImageTextureLibraryData libraryData = (FreeImageTextureLibraryData) image.LibraryData[this];
52  if (libraryData.Data != IntPtr.Zero) Marshal.FreeHGlobal(libraryData.Data);
53  }
54 
55  public void StartLibrary(TexImage image)
56  {
57  if(Tools.IsCompressed(image.Format))
58  {
59  Log.Error("FreeImage can't process compressed texture.");
60  throw new TextureToolsException("FreeImage can't process compressed texture.");
61  }
62 
63  FreeImageTextureLibraryData libraryData = new FreeImageTextureLibraryData();
64  image.LibraryData[this] = libraryData;
65 
66  libraryData.Bitmaps = new FIBITMAP[image.SubImageArray.Length];
67  uint bpp = Tools.GetBPP(image.Format);
68 
69  for (int i = 0; i < image.SubImageArray.Length; ++i)
70  {
71  libraryData.Bitmaps[i] = FreeImage.ConvertFromRawBits(image.SubImageArray[i].Data, FREE_IMAGE_TYPE.FIT_BITMAP, image.SubImageArray[i].Width, image.SubImageArray[i].Height, image.SubImageArray[i].RowPitch, bpp, 0x00FF0000, 0x0000FF00, 0x000000FF, false);
72  }
73 
74  if (image.DisposingLibrary != null) image.DisposingLibrary.Dispose(image);
75 
76  image.DisposingLibrary = this;
77 
78  libraryData.Data = IntPtr.Zero;
79  }
80 
81  public void EndLibrary(TexImage image)
82  {
83  if (!image.LibraryData.ContainsKey(this)) return;
84  FreeImageTextureLibraryData libraryData = (FreeImageTextureLibraryData)image.LibraryData[this];
85 
86  IntPtr buffer = Marshal.AllocHGlobal(image.DataSize);
87  int offset = 0;
88  int size, rowPitch, slicePitch;
89 
90  image.SubImageArray = new TexImage.SubImage[libraryData.Bitmaps.Length];
91 
92  try
93  {
94  for (int i = 0; i < libraryData.Bitmaps.Length; ++i)
95  {
96  image.SubImageArray[i].Width = (int)FreeImage.GetWidth(libraryData.Bitmaps[i]);
97  image.SubImageArray[i].Height = (int)FreeImage.GetHeight(libraryData.Bitmaps[i]);
98 
99  Tools.ComputePitch(image.Format, image.SubImageArray[i].Width, image.SubImageArray[i].Height, out rowPitch, out slicePitch);
100  size = slicePitch;
101 
102  image.SubImageArray[i].Data = new IntPtr(buffer.ToInt64() + offset);
103  image.SubImageArray[i].DataSize = size;
104  image.SubImageArray[i].RowPitch = rowPitch;
105  image.SubImageArray[i].SlicePitch = slicePitch;
106 
107  Utilities.CopyMemory(image.SubImageArray[i].Data, FreeImage.GetBits(libraryData.Bitmaps[i]), size);
108  offset += size;
109  }
110  }
111  catch (AccessViolationException e)
112  {
113  Log.Error("Failed to convert FreeImage native data to TexImage texture. ", e);
114  throw new TextureToolsException("Failed to convert FreeImage native data to TexImage texture. ", e);
115  }
116 
117  image.Data = image.SubImageArray[0].Data;
118  libraryData.Data = image.Data;
119 
120  // Freeing native Data, the texture data has been copied.
121  for (int i = 0; i < libraryData.Bitmaps.Length; ++i)
122  {
123  FreeImage.Unload(libraryData.Bitmaps[i]);
124  }
125 
126  libraryData.Bitmaps = null;
127  image.DisposingLibrary = this;
128  }
129 
130  public bool CanHandleRequest(TexImage image, IRequest request)
131  {
132  switch (request.Type)
133  {
134  case RequestType.Loading:
135  {
136  LoadingRequest loader = (LoadingRequest)request;
137  FREE_IMAGE_FORMAT format = FreeImage.GetFIFFromFilename(loader.FilePath);
138  return format != FREE_IMAGE_FORMAT.FIF_UNKNOWN && format != FREE_IMAGE_FORMAT.FIF_DDS; // FreeImage can load DDS texture, but can't handle their mipmaps..
139  }
140  case RequestType.Export:
141  {
142  ExportRequest export = (ExportRequest)request;
143  FREE_IMAGE_FORMAT format = FreeImage.GetFIFFromFilename(export.FilePath);
144  return format != FREE_IMAGE_FORMAT.FIF_UNKNOWN && format != FREE_IMAGE_FORMAT.FIF_DDS;
145  }
146  case RequestType.Rescaling:
147  RescalingRequest rescale = (RescalingRequest)request;
148  return rescale.Filter != Filter.Rescaling.Nearest;
149 
150  case RequestType.SwitchingChannels:
151  case RequestType.GammaCorrection:
152  case RequestType.Flipping:
153  case RequestType.FlippingSub:
154  case RequestType.Swapping:
155  return true;
156  default:
157  return false;
158  }
159  }
160 
161  public void Execute(TexImage image, IRequest request)
162  {
163  FreeImageTextureLibraryData libraryData = image.LibraryData.ContainsKey(this) ? (FreeImageTextureLibraryData)image.LibraryData[this] : null;
164 
165  switch (request.Type)
166  {
167  case RequestType.Loading:
168  Load(image, libraryData, (LoadingRequest)request);
169  break;
170 
171  case RequestType.Rescaling:
172  Rescale(image, libraryData, (RescalingRequest)request);
173  break;
174 
175  case RequestType.SwitchingChannels:
176  SwitchChannels(image, libraryData, (SwitchingBRChannelsRequest)request);
177  break;
178 
179  case RequestType.Flipping:
180  Flip(image, libraryData, (FlippingRequest)request);
181  break;
182 
183  case RequestType.FlippingSub:
184  FlipSub(image, libraryData, (FlippingSubRequest)request);
185  break;
186 
187  case RequestType.Swapping:
188  Swap(image, libraryData, (SwappingRequest)request);
189  break;
190 
191  case RequestType.Export:
192  Export(image, libraryData, (ExportRequest)request);
193  break;
194 
195  case RequestType.GammaCorrection:
196  CorrectGamma(image, libraryData, (GammaCorrectionRequest)request);
197  break;
198 
199  default:
200  Log.Error("FITexLib (FreeImage) can't handle this request: " + request.Type);
201  throw new TextureToolsException("FITexLib (FreeImage) can't handle this request: " + request.Type);
202  }
203  }
204 
205 
206  /// <summary>
207  /// Loads the specified image.
208  /// </summary>
209  /// <param name="image">The image.</param>
210  /// <param name="libraryData">The library data.</param>
211  /// <param name="loader">The loader.</param>
212  /// <exception cref="TextureToolsException">If loading failed : mostly not supported format or path error (FileNotFound).</exception>
213  private void Load(TexImage image, FreeImageTextureLibraryData libraryData, LoadingRequest loader)
214  {
215  Log.Info("Loading " + loader.FilePath + " ...");
216 
217  FIBITMAP temp;
218  try
219  {
220  temp = FreeImage.LoadEx(loader.FilePath);
221  FreeImage.FlipVertical(temp);
222 
223  if (temp.IsNull)
224  throw new Exception("FreeImage's image data is null");
225  }
226  catch (Exception e)
227  {
228  Log.Error("Loading file " + loader.FilePath + " failed: " + e.Message);
229  throw new TextureToolsException("Loading file " + loader.FilePath + " failed: " + e.Message);
230  }
231 
232  // Converting the image into BGRA_8888 format
233  libraryData = new FreeImageTextureLibraryData { Bitmaps = new FIBITMAP[1] { FreeImage.ConvertTo32Bits(temp) } };
234  image.LibraryData[this] = libraryData;
235 
236  FreeImage.Unload(temp);
237 
238  image.Data = FreeImage.GetBits(libraryData.Bitmaps[0]);
239  image.Width = (int)FreeImage.GetWidth(libraryData.Bitmaps[0]);
240  image.Height = (int)FreeImage.GetHeight(libraryData.Bitmaps[0]);
241  image.Depth = 1;
242  image.Dimension = image.Height == 1 ? TexImage.TextureDimension.Texture1D : TexImage.TextureDimension.Texture2D;
243  image.Format = SiliconStudio.Paradox.Graphics.PixelFormat.B8G8R8A8_UNorm;
244 
245  int rowPitch, slicePitch;
246  Tools.ComputePitch(image.Format, image.Width, image.Height, out rowPitch, out slicePitch);
247  image.RowPitch = rowPitch;
248  image.SlicePitch = slicePitch;
249 
250  //Only one image in the SubImageArray, FreeImage is only used to load images, not textures.
251  image.SubImageArray[0].Data = image.Data;
252  image.SubImageArray[0].DataSize = image.DataSize;
253  image.SubImageArray[0].Width = image.Width;
254  image.SubImageArray[0].Height = image.Height;
255  image.SubImageArray[0].RowPitch = rowPitch;
256  image.SubImageArray[0].SlicePitch = slicePitch;
257  image.DataSize = (int) (FreeImage.GetDIBSize(libraryData.Bitmaps[0]) - GetHeaderSize()); // header size of a bitmap is included in their size calculus
258  libraryData.Data = IntPtr.Zero;
259  image.DisposingLibrary = this;
260  }
261 
262  /// <summary>
263  /// Gets the size of the header of a Bitmap.
264  /// </summary>
265  /// <returns></returns>
266  private unsafe uint GetHeaderSize()
267  {
268  return (uint)sizeof(BITMAPINFOHEADER);
269  }
270 
271 
272  /// <summary>
273  /// Rescales the specified image.
274  /// </summary>
275  /// <remarks>
276  /// The MipmapCount will be reset to 1 after this operation
277  /// </remarks>
278  /// <param name="image">The image.</param>
279  /// <param name="libraryData">The library data.</param>
280  /// <param name="rescale">The rescale.</param>
281  private void Rescale(TexImage image, FreeImageTextureLibraryData libraryData, RescalingRequest rescale)
282  {
283  int width = rescale.ComputeWidth(image);
284  int height = rescale.ComputeHeight(image);
285 
286  Log.Info("Rescaling image to " + width + "x" + height + " with " + rescale.Filter + " ...");
287 
288  FIBITMAP[] newTab;
289 
290  if (image.Dimension == TexImage.TextureDimension.Texture3D) // in case of 3D Texture, we must rescale each slice of the top mipmap level
291  {
292  newTab = new FIBITMAP[image.ArraySize * image.FaceCount * image.Depth];
293  int curDepth;
294 
295  int nbSubImageWithMipMapPerArrayMemeber = 0; // calculating the number of sub images we have to jump to reach the next top level mipmap of the next array member
296  curDepth = image.Depth;
297  for (int i = 0; i < image.MipmapCount; ++i)
298  {
299  nbSubImageWithMipMapPerArrayMemeber += curDepth;
300  curDepth = curDepth > 1 ? curDepth >>= 1 : curDepth;
301  }
302 
303  int ct = 0;
304  for (int j = 0; j < image.ArraySize; ++j)
305  {
306  for (int i = 0; i < image.Depth; ++i)
307  {
308  newTab[ct] = FreeImage.Rescale(libraryData.Bitmaps[i + j * nbSubImageWithMipMapPerArrayMemeber], width, height, (FREE_IMAGE_FILTER)rescale.Filter);
309  ++ct;
310  }
311  }
312  }
313  else
314  {
315  newTab = new FIBITMAP[image.ArraySize];
316  int ct = 0;
317  for (int i = 0; i < libraryData.Bitmaps.Length; i += image.MipmapCount)
318  {
319  newTab[ct] = FreeImage.Rescale(libraryData.Bitmaps[i], width, height, (FREE_IMAGE_FILTER)rescale.Filter);
320  ++ct;
321  }
322  }
323 
324  for (int i = 0; i < libraryData.Bitmaps.Length; ++i)
325  {
326  FreeImage.Unload(libraryData.Bitmaps[i]);
327  }
328 
329  libraryData.Bitmaps = newTab;
330  image.Data = FreeImage.GetBits(newTab[0]);
331 
332  // Updating image data
333  image.Rescale(width, height);
334 
335  int rowPitch, slicePitch;
336  Tools.ComputePitch(image.Format, width, height, out rowPitch, out slicePitch);
337 
338  image.RowPitch = rowPitch;
339  image.SlicePitch = slicePitch;
340  image.MipmapCount = 1;
341  image.DataSize = image.SlicePitch * image.ArraySize * image.FaceCount * image.Depth;
342  }
343 
344 
345  /// <summary>
346  /// Switches the channels R and B.
347  /// </summary>
348  /// <param name="image">The image.</param>
349  /// <param name="libraryData">The library data.</param>
350  /// <param name="switchC">The switch request</param>
351  /// <remarks>
352  /// Some libraries can't handle BGRA order so we need to change it to RGBA.
353  /// </remarks>
354  private void SwitchChannels(TexImage image, FreeImageTextureLibraryData libraryData, SwitchingBRChannelsRequest switchC)
355  {
356 
357  Log.Info("Switching channels R and G ...");
358 
359  for (int i = 0; i < libraryData.Bitmaps.Length; ++i)
360  {
361  FIBITMAP blueChannel = FreeImage.GetChannel(libraryData.Bitmaps[i], FREE_IMAGE_COLOR_CHANNEL.FICC_BLUE);
362  FIBITMAP redChannel = FreeImage.GetChannel(libraryData.Bitmaps[i], FREE_IMAGE_COLOR_CHANNEL.FICC_RED);
363  FreeImage.SetChannel(libraryData.Bitmaps[i], redChannel, FREE_IMAGE_COLOR_CHANNEL.FICC_BLUE);
364  FreeImage.SetChannel(libraryData.Bitmaps[i], blueChannel, FREE_IMAGE_COLOR_CHANNEL.FICC_RED);
365  FreeImage.Unload(blueChannel);
366  FreeImage.Unload(redChannel);
367  }
368 
369  if (Tools.IsInBGRAOrder(image.Format))
370  image.Format = SiliconStudio.Paradox.Graphics.PixelFormat.R8G8B8A8_UNorm;
371  else
372  image.Format = SiliconStudio.Paradox.Graphics.PixelFormat.B8G8R8A8_UNorm;
373  }
374 
375  public bool SupportBGRAOrder()
376  {
377  return true;
378  }
379 
380 
381  /// <summary>
382  /// Flips the specified image horizontally or vertically.
383  /// </summary>
384  /// <param name="image">The image.</param>
385  /// <param name="libraryData">The library data.</param>
386  /// <param name="flip">The flip request.</param>
387  private void Flip(TexImage image, FreeImageTextureLibraryData libraryData, FlippingRequest flip)
388  {
389  Log.Info("Flipping image : " + flip.Flip + " ...");
390 
391  for (int i = 0; i < libraryData.Bitmaps.Length; ++i)
392  {
393  switch (flip.Flip)
394  {
395  case Orientation.Vertical:
396  FreeImage.FlipVertical(libraryData.Bitmaps[i]);
397  break;
398 
399  case Orientation.Horizontal:
400  FreeImage.FlipHorizontal(libraryData.Bitmaps[i]);
401  break;
402  }
403  }
404 
405  image.Flip(flip.Flip);
406  }
407 
408 
409  /// <summary>
410  /// Flips the specified sub-image horizontally or vertically.
411  /// </summary>
412  /// <param name="image">The image.</param>
413  /// <param name="libraryData">The library data.</param>
414  /// <param name="flipSub">The flip request.</param>
415  private void FlipSub(TexImage image, FreeImageTextureLibraryData libraryData, FlippingSubRequest flipSub)
416  {
417  Log.Info("Flipping image : sub-image " + flipSub.SubImageIndex + " " + flipSub.Flip + " ...");
418 
419  if (flipSub.SubImageIndex >= 0 && flipSub.SubImageIndex < libraryData.Bitmaps.Length)
420  {
421  switch (flipSub.Flip)
422  {
423  case Orientation.Vertical:
424  FreeImage.FlipVertical(libraryData.Bitmaps[flipSub.SubImageIndex]);
425  break;
426 
427  case Orientation.Horizontal:
428  FreeImage.FlipHorizontal(libraryData.Bitmaps[flipSub.SubImageIndex]);
429  break;
430  }
431  }
432  else
433  {
434  Log.Warning("Cannot flip the sub-image " + flipSub.SubImageIndex + " because there is only " + libraryData.Bitmaps.Length + " sub-images.");
435  }
436 
437  // TODO: texture atlas update?
438  }
439 
440 
441  /// <summary>
442  /// Swaps two sub-images.
443  /// </summary>
444  /// <param name="image">The image.</param>
445  /// <param name="libraryData">The library data.</param>
446  /// <param name="swap">The swap request.</param>
447  private void Swap(TexImage image, FreeImageTextureLibraryData libraryData, SwappingRequest swap)
448  {
449  Log.Info("Swapping image : sub-image " + swap.FirstSubImageIndex + " and " + swap.SecondSubImageIndex + " ...");
450 
451  if (swap.FirstSubImageIndex >= 0 && swap.FirstSubImageIndex < libraryData.Bitmaps.Length
452  && swap.SecondSubImageIndex >= 0 && swap.SecondSubImageIndex < libraryData.Bitmaps.Length)
453  {
454  // copy first image
455  var firstImage = FreeImage.Copy(libraryData.Bitmaps[swap.FirstSubImageIndex], 0, 0, image.Width, image.Height);
456  FreeImage.Paste(libraryData.Bitmaps[swap.FirstSubImageIndex], libraryData.Bitmaps[swap.SecondSubImageIndex], 0, 0, 256);
457  FreeImage.Paste(libraryData.Bitmaps[swap.SecondSubImageIndex], firstImage, 0, 0, 256);
458 
459  // TODO: free firstImage?
460  }
461  else
462  {
463  Log.Warning("Cannot swap the sub-images " + swap.FirstSubImageIndex + " and " + swap.SecondSubImageIndex + " because there is only " + libraryData.Bitmaps.Length + " sub-images.");
464  }
465 
466  // TODO: texture atlas update?
467  }
468 
469 
470  /// <summary>
471  /// Exports the specified image to the requested file name.
472  /// </summary>
473  /// <param name="image">The image.</param>
474  /// <param name="libraryData">The library data.</param>
475  /// <param name="request">The request.</param>
476  /// <exception cref="TexLibraryException">
477  /// Export failure.
478  /// </exception>
479  /// <remarks>
480  /// In case of mipmapping or array texture, may images will be output.
481  /// </remarks>
482  private void Export(TexImage image, FreeImageTextureLibraryData libraryData, ExportRequest request)
483  {
484  String directory = Path.GetDirectoryName(request.FilePath);
485  String fileName = Path.GetFileNameWithoutExtension(request.FilePath);
486  String extension = Path.GetExtension(request.FilePath);
487  String finalName;
488 
489  if (image.Dimension == TexImage.TextureDimension.Texture3D)
490  {
491  Log.Error("Not implemented.");
492  throw new TextureToolsException("Not implemented.");
493  }
494 
495  if(!Tools.IsInBGRAOrder(image.Format))
496  {
497  SwitchChannels(image, libraryData, new SwitchingBRChannelsRequest());
498  }
499 
500  if (image.SubImageArray.Length > 1 && request.MinimumMipMapSize < FreeImage.GetWidth(libraryData.Bitmaps[0]) && request.MinimumMipMapSize < FreeImage.GetHeight(libraryData.Bitmaps[0]))
501  {
502  int imageCount = 0;
503  for (int i = 0; i < image.ArraySize; ++i)
504  {
505  for (int j = 0; j < image.MipmapCount; ++j)
506  {
507  if (FreeImage.GetWidth(libraryData.Bitmaps[imageCount]) < request.MinimumMipMapSize || FreeImage.GetHeight(libraryData.Bitmaps[imageCount]) < request.MinimumMipMapSize)
508  break;
509 
510  finalName = directory + "/" + fileName + "-ind_" + i + "-mip_" + j + extension;
511  FreeImage.FlipVertical(libraryData.Bitmaps[imageCount]);
512  if (!FreeImage.SaveEx(libraryData.Bitmaps[imageCount], finalName))
513  {
514  Log.Error("Export failure.");
515  throw new TextureToolsException("Export failure.");
516  }
517  FreeImage.FlipVertical(libraryData.Bitmaps[imageCount]);
518  Log.Info("Exporting image to " + finalName + " ...");
519  ++imageCount;
520  }
521  }
522  }
523  else
524  {
525  FreeImage.FlipVertical(libraryData.Bitmaps[0]);
526  if (!FreeImage.SaveEx(libraryData.Bitmaps[0], request.FilePath))
527  {
528  Log.Error("Export failure.");
529  throw new TextureToolsException("Export failure.");
530  }
531  FreeImage.FlipVertical(libraryData.Bitmaps[0]);
532  Log.Info("Exporting image to " + request.FilePath + " ...");
533  }
534 
535  image.Save(request.FilePath);
536  }
537 
538 
539  /// <summary>
540  /// Corrects the gamma.
541  /// </summary>
542  /// <param name="image">The image.</param>
543  /// <param name="libraryData">The library data.</param>
544  /// <param name="request">The request.</param>
545  public void CorrectGamma(TexImage image, FreeImageTextureLibraryData libraryData, GammaCorrectionRequest request)
546  {
547  Log.Info("Applying a gamma correction of " + request.Gamma + " ...");
548 
549  foreach (FIBITMAP bitmap in libraryData.Bitmaps)
550  {
551  FreeImage.AdjustGamma(bitmap, request.Gamma);
552  }
553  }
554 
555  }
556 }
The FIBITMAP structure is a handle to a FreeImage bimtap.
Definition: FIBITMAP.cs:50
Base implementation for ILogger.
Definition: Logger.cs:10
switch(inFormat)
FREE_IMAGE_FILTER
Upsampling / downsampling filters. Constants used in FreeImage_Rescale.
This structure contains information about the dimensions and color format of a device-independent bit...
_In_ size_t _In_ size_t _In_ DXGI_FORMAT format
Definition: DirectXTexP.h:175
FREE_IMAGE_FORMAT
I/O image format identifiers.
_In_ size_t _In_ size_t size
Definition: DirectXTexP.h:175
Output message to log right away.