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