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