[NUI] TCSACR-226 code change (#1032)
[platform/core/csapi/tizenfx.git] / src / Tizen.Multimedia.Util / ImageUtil / ImageDecoder.cs
1 /*
2  * Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  * Licensed under the Apache License, Version 2.0 (the License);
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an AS IS BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 using System;
18 using System.Collections.Generic;
19 using System.Diagnostics;
20 using System.IO;
21 using System.Linq;
22 using System.Runtime.InteropServices;
23 using System.Threading;
24 using System.Threading.Tasks;
25 using static Interop;
26 using NativeUtil = Interop.ImageUtil;
27 using NativeDecoder = Interop.ImageUtil.Decode;
28
29 namespace Tizen.Multimedia.Util
30 {
31     /// <summary>
32     /// This is a base class for image decoders.
33     /// </summary>
34     /// <since_tizen> 4 </since_tizen>
35     public abstract class ImageDecoder : IDisposable
36     {
37         private ImageDecoderHandle _handle;
38
39         private ColorSpace? _colorSpace;
40
41         internal ImageDecoder(ImageFormat format)
42         {
43             NativeDecoder.Create(out _handle).ThrowIfFailed("Failed to create ImageDecoder");
44
45             Debug.Assert(_handle != null);
46
47             InputFormat = format;
48         }
49
50         /// <summary>
51         /// Gets the image format of this decoder.
52         /// </summary>
53         /// <since_tizen> 4 </since_tizen>
54         public ImageFormat InputFormat { get; }
55
56         private ImageDecoderHandle Handle
57         {
58             get
59             {
60                 if (_handle.IsInvalid)
61                 {
62                     throw new ObjectDisposedException(nameof(ImageDecoder));
63                 }
64                 return _handle;
65             }
66         }
67
68         /// <summary>
69         /// Sets the color-space to decode into. The default is <see cref="ColorSpace.Rgba8888"/>.
70         /// </summary>
71         /// <param name="colorSpace">The value indicating color-space to decode into.</param>
72         /// <exception cref="ArgumentException"><paramref name="colorSpace"/> is invalid.</exception>
73         /// <exception cref="NotSupportedException"><paramref name="colorSpace"/> is not supported by the decoder.</exception>
74         /// <seealso cref="ImageUtil.GetSupportedColorSpaces(ImageFormat)"/>
75         /// <since_tizen> 4 </since_tizen>
76         public void SetColorSpace(ColorSpace colorSpace)
77         {
78             ValidationUtil.ValidateEnum(typeof(ColorSpace), colorSpace, nameof(ColorSpace));
79
80             if (ImageUtil.GetSupportedColorSpaces(InputFormat).Contains(colorSpace) == false)
81             {
82                 throw new NotSupportedException($"{colorSpace.ToString()} is not supported for {InputFormat}.");
83             }
84
85             _colorSpace = colorSpace;
86         }
87
88         /// <summary>
89         /// Decodes an image from the specified file.
90         /// </summary>
91         /// <param name="inputFilePath">The input file path from which to decode.</param>
92         /// <returns>A task that represents the asynchronous decoding operation.</returns>
93         /// <remarks>
94         ///     Only Graphics Interchange Format(GIF) codec returns more than one frame.<br/>
95         ///     <br/>
96         ///     http://tizen.org/privilege/mediastorage is needed if <paramref name="inputFilePath"/> is relevant to the media storage.<br/>
97         ///     http://tizen.org/privilege/externalstorage is needed if <paramref name="inputFilePath"/> is relevant to the external storage.
98         /// </remarks>
99         /// <exception cref="ArgumentNullException"><paramref name="inputFilePath"/> is null.</exception>
100         /// <exception cref="ArgumentException">
101         ///     <paramref name="inputFilePath"/> is an empty string.<br/>
102         ///     -or-<br/>
103         ///     <paramref name="inputFilePath"/> is not a image file.
104         /// </exception>
105         /// <exception cref="FileNotFoundException"><paramref name="inputFilePath"/> does not exists.</exception>
106         /// <exception cref="UnauthorizedAccessException">The caller does not have required permission to access the path.</exception>
107         /// <exception cref="FileFormatException">The format of <paramref name="inputFilePath"/> is not <see cref="InputFormat"/>.</exception>
108         /// <exception cref="ObjectDisposedException">The <see cref="ImageDecoder"/> has already been disposed of.</exception>
109         /// <since_tizen> 4 </since_tizen>
110         public async Task<IEnumerable<BitmapFrame>> DecodeAsync(string inputFilePath)
111         {
112             if (inputFilePath == null)
113             {
114                 throw new ArgumentNullException(nameof(inputFilePath));
115             }
116
117             if (inputFilePath.Length == 0)
118             {
119                 throw new ArgumentException("path is empty.", nameof(inputFilePath));
120             }
121
122             if (CheckHeader(inputFilePath) == false)
123             {
124                 throw new FileFormatException("The file has an invalid header.");
125             }
126
127             var pathPtr = Marshal.StringToHGlobalAnsi(inputFilePath);
128             try
129             {
130                 NativeDecoder.SetInputPath(Handle, pathPtr).ThrowIfFailed("Failed to set input file path for decoding");
131
132                 return await DecodeAsync();
133             }
134             finally
135             {
136                 Marshal.FreeHGlobal(pathPtr);
137             }
138         }
139
140         /// <summary>
141         /// Decodes an image from the buffer.
142         /// </summary>
143         /// <param name="inputBuffer">The image buffer from which to decode.</param>
144         /// <returns>A task that represents the asynchronous decoding operation.</returns>
145         /// <remarks>Only Graphics Interchange Format(GIF) codec returns more than one frame.</remarks>
146         /// <exception cref="ArgumentNullException"><paramref name="inputBuffer"/> is null.</exception>
147         /// <exception cref="ArgumentException"><paramref name="inputBuffer"/> is an empty array.</exception>
148         /// <exception cref="FileFormatException">The format of <paramref name="inputBuffer"/> is not <see cref="InputFormat"/>.</exception>
149         /// <exception cref="ObjectDisposedException">The <see cref="ImageDecoder"/> has already been disposed of.</exception>
150         /// <since_tizen> 4 </since_tizen>
151         public Task<IEnumerable<BitmapFrame>> DecodeAsync(byte[] inputBuffer)
152         {
153             if (inputBuffer == null)
154             {
155                 throw new ArgumentNullException(nameof(inputBuffer));
156             }
157
158             if (inputBuffer.Length == 0)
159             {
160                 throw new ArgumentException("buffer is empty.", nameof(inputBuffer));
161             }
162
163             if (CheckHeader(inputBuffer) == false)
164             {
165                 throw new FileFormatException("buffer has an invalid header.");
166             }
167
168             NativeDecoder.SetInputBuffer(Handle, inputBuffer, (ulong)inputBuffer.Length).
169                 ThrowIfFailed("Failed to set input buffer for decoding");
170
171             return DecodeAsync();
172         }
173
174         private bool CheckHeader(byte[] input)
175         {
176             if (input.Length < Header.Length)
177             {
178                 return false;
179             }
180
181             for (int i = 0; i < Header.Length; ++i)
182             {
183                 if (input[i] != Header[i])
184                 {
185                     return false;
186                 }
187             }
188
189             return true;
190         }
191
192         private bool CheckHeader(string inputFile)
193         {
194             using (var fs = File.OpenRead(inputFile))
195             {
196                 byte[] fileHeader = new byte[Header.Length];
197
198                 if (fs.Read(fileHeader, 0, fileHeader.Length) < Header.Length)
199                 {
200                     return false;
201                 }
202                 return CheckHeader(fileHeader);
203             }
204         }
205
206         private IEnumerable<BitmapFrame> RunDecoding()
207         {
208             IntPtr imageHandle = IntPtr.Zero;
209             IntPtr outBuffer = IntPtr.Zero;
210
211             try
212             {
213                 NativeDecoder.DecodeRun(Handle, out imageHandle).ThrowIfFailed("Failed to decode");
214
215                 NativeUtil.GetImage(imageHandle, out int width, out int height, out ImageColorSpace colorspace,
216                     out outBuffer, out int size).ThrowIfFailed("Failed to get decoded image.");
217
218                 yield return new BitmapFrame(outBuffer, width, height, size);
219             }
220             finally
221             {
222                 if (outBuffer != IntPtr.Zero)
223                 {
224                     LibcSupport.Free(outBuffer);
225                 }
226
227                 if (imageHandle != IntPtr.Zero)
228                 {
229                     NativeUtil.Destroy(imageHandle).ThrowIfFailed("Failed to destroy handle");
230                 }
231             }
232         }
233
234         internal Task<IEnumerable<BitmapFrame>> DecodeAsync()
235         {
236             Initialize(Handle);
237
238             return Task.Factory.StartNew(RunDecoding, CancellationToken.None,
239                 TaskCreationOptions.DenyChildAttach | TaskCreationOptions.LongRunning,
240                 TaskScheduler.Default);
241         }
242
243         internal virtual void Initialize(ImageDecoderHandle handle)
244         {
245             if (_colorSpace.HasValue)
246             {
247                 NativeDecoder.SetColorspace(Handle, _colorSpace.Value.ToImageColorSpace()).ThrowIfFailed("Failed to set color space");
248             }
249         }
250
251         internal abstract byte[] Header { get; }
252
253         #region IDisposable Support
254         private bool _disposed = false;
255
256         /// <summary>
257         /// Releases the unmanaged resources used by the ImageDecoder.
258         /// </summary>
259         /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
260         /// <since_tizen> 4 </since_tizen>
261         protected virtual void Dispose(bool disposing)
262         {
263             if (!_disposed)
264             {
265                 if (_handle != null)
266                 {
267                     _handle.Dispose();
268                 }
269                 _disposed = true;
270             }
271         }
272
273         /// <summary>
274         /// Releases all resources used by the ImageDecoder.
275         /// </summary>
276         /// <since_tizen> 4 </since_tizen>
277         public void Dispose()
278         {
279             Dispose(true);
280         }
281         #endregion
282     }
283
284     /// <summary>
285     /// Provides the ability to decode Bitmap (BMP) encoded images.
286     /// </summary>
287     /// <since_tizen> 4 </since_tizen>
288     public class BmpDecoder : ImageDecoder
289     {
290         private static readonly byte[] _header = { (byte)'B', (byte)'M' };
291
292         /// <summary>
293         /// Initializes a new instance of the <see cref="BmpDecoder"/> class.
294         /// </summary>
295         /// <remarks><see cref="ImageDecoder.InputFormat"/> will be the <see cref="ImageFormat.Bmp"/>.</remarks>
296         /// <since_tizen> 4 </since_tizen>
297         public BmpDecoder() : base(ImageFormat.Bmp)
298         {
299         }
300
301         internal override byte[] Header => _header;
302     }
303
304     /// <summary>
305     /// Provides the ability to decode the Portable Network Graphics (PNG) encoded images.
306     /// </summary>
307     /// <since_tizen> 4 </since_tizen>
308     public class PngDecoder : ImageDecoder
309     {
310         private static readonly byte[] _header = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
311
312         /// <summary>
313         /// Initializes a new instance of the <see cref="PngDecoder"/> class.
314         /// </summary>
315         /// <remarks><see cref="ImageDecoder.InputFormat"/> will be the <see cref="ImageFormat.Png"/>.</remarks>
316         /// <since_tizen> 4 </since_tizen>
317         public PngDecoder() : base(ImageFormat.Png)
318         {
319         }
320
321         internal override byte[] Header => _header;
322     }
323
324     /// <summary>
325     /// Provides the ability to decode the Joint Photographic Experts Group (JPEG) encoded images.
326     /// </summary>
327     /// <since_tizen> 4 </since_tizen>
328     public class JpegDecoder : ImageDecoder
329     {
330         private static readonly byte[] _header = { 0xFF, 0xD8 };
331
332         /// <summary>
333         /// A read-only field that represents the default value of <see cref="Downscale"/>.
334         /// </summary>
335         /// <since_tizen> 4 </since_tizen>
336         public static readonly JpegDownscale DefaultJpegDownscale = JpegDownscale.None;
337
338         private JpegDownscale _jpegDownscale = DefaultJpegDownscale;
339
340         /// <summary>
341         /// Initializes a new instance of the <see cref="JpegDecoder"/> class.
342         /// </summary>
343         /// <remarks><see cref="ImageDecoder.InputFormat"/> will be the <see cref="ImageFormat.Jpeg"/>.</remarks>
344         /// <since_tizen> 4 </since_tizen>
345         public JpegDecoder() : base(ImageFormat.Jpeg)
346         {
347         }
348
349         /// <summary>
350         /// Gets or sets the downscale at which the jpeg image should be decoded.
351         /// </summary>
352         /// <exception cref="ArgumentException"><paramref name="value"/> is invalid.</exception>
353         /// <since_tizen> 4 </since_tizen>
354         public JpegDownscale Downscale
355         {
356             get
357             {
358                 return _jpegDownscale;
359             }
360             set
361             {
362                 ValidationUtil.ValidateEnum(typeof(JpegDownscale), value, nameof(Downscale));
363
364                 _jpegDownscale = value;
365             }
366         }
367
368         internal override void Initialize(ImageDecoderHandle handle)
369         {
370             base.Initialize(handle);
371
372             NativeDecoder.SetJpegDownscale(handle, Downscale).ThrowIfFailed("Failed to set downscale for decoding");
373         }
374
375         internal override byte[] Header => _header;
376     }
377
378     /// <summary>
379     /// Provides the ability to decode the Graphics Interchange Format (GIF) encoded images.
380     /// </summary>
381     /// <since_tizen> 4 </since_tizen>
382     public class GifDecoder : ImageDecoder
383     {
384         private static readonly byte[] _header = { (byte)'G', (byte)'I', (byte)'F' };
385
386         /// <summary>
387         /// Initializes a new instance of the <see cref="GifDecoder"/> class.
388         /// </summary>
389         /// <remarks><see cref="ImageDecoder.InputFormat"/> will be the <see cref="ImageFormat.Gif"/>.</remarks>
390         /// <since_tizen> 4 </since_tizen>
391         public GifDecoder() : base(ImageFormat.Gif)
392         {
393         }
394
395         internal override byte[] Header => _header;
396     }
397 }