Release 4.0.0-preview1-00201
[platform/core/csapi/tizenfx.git] / src / Tizen.Multimedia.Util / ImageUtil / ImageEncoder.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.ImageUtil;
25 using Unmanaged = Interop.ImageUtil.Encode;
26
27 namespace Tizen.Multimedia.Util
28 {
29     /// <summary>
30     /// This is a base class for image encoders.
31     /// </summary>
32     public abstract class ImageEncoder : IDisposable
33     {
34         private ImageEncoderHandle _handle;
35
36         private bool _hasResolution;
37
38         internal ImageEncoder(ImageFormat format)
39         {
40             Unmanaged.Create(format, out _handle).ThrowIfFailed("Failed to create ImageEncoder");
41
42             Debug.Assert(_handle != null);
43
44             OutputFormat = format;
45         }
46
47         private ImageEncoderHandle Handle
48         {
49             get
50             {
51                 if (_disposed)
52                 {
53                     throw new ObjectDisposedException(GetType().Name);
54                 }
55
56                 return _handle;
57             }
58         }
59
60         /// <summary>
61         /// Gets the image format of this encoder.
62         /// </summary>
63         public ImageFormat OutputFormat { get; }
64
65         /// <summary>
66         /// Sets the resolution of the output image.
67         /// </summary>
68         /// <param name="resolution">The target resolution.</param>
69         /// <exception cref="ArgumentOutOfRangeException">
70         ///     The width of <paramref name="resolution"/> is less than or equal to zero.\n
71         ///     - or -\n
72         ///     The height of <paramref name="resolution"/> is less than or equal to zero.
73         /// </exception>
74         public void SetResolution(Size resolution)
75         {
76             if (resolution.Width <= 0)
77             {
78                 throw new ArgumentOutOfRangeException(nameof(resolution), resolution.Width,
79                     "The width of resolution can't be less than or equal to zero.");
80             }
81             if (resolution.Height <= 0)
82             {
83                 throw new ArgumentOutOfRangeException(nameof(resolution), resolution.Height,
84                     "The height of resolution can't be less than or equal to zero.");
85             }
86
87             Unmanaged.SetResolution(Handle, (uint)resolution.Width, (uint)resolution.Height).
88                 ThrowIfFailed("Failed to set the resolution");
89
90             _hasResolution = true;
91         }
92
93         /// <summary>
94         /// Sets the color-space of the output image.
95         /// </summary>
96         /// <param name="colorSpace">The target color-space.</param>
97         /// <exception cref="ArgumentException"><paramref name="colorSpace"/> is invalid.</exception>
98         /// <exception cref="NotSupportedException"><paramref name="colorSpace"/> is not supported by the encoder.</exception>
99         /// <seealso cref="ImageUtil.GetSupportedColorSpaces(ImageFormat)"/>
100         public void SetColorSpace(ColorSpace colorSpace)
101         {
102             ValidationUtil.ValidateEnum(typeof(ColorSpace), colorSpace, nameof(colorSpace));
103
104             if (ImageUtil.GetSupportedColorSpaces(OutputFormat).Contains(colorSpace) == false)
105             {
106                 throw new NotSupportedException($"{colorSpace.ToString()} is not supported for {OutputFormat}.");
107             }
108
109             Unmanaged.SetColorspace(Handle, colorSpace.ToImageColorSpace()).
110                 ThrowIfFailed("Failed to set the color space");
111         }
112
113         private Task Run(Stream outStream)
114         {
115             var tcs = new TaskCompletionSource<bool>();
116
117             IntPtr outBuffer = IntPtr.Zero;
118             Unmanaged.SetOutputBuffer(Handle, out outBuffer).ThrowIfFailed("Failed to initialize encoder");
119
120             Task.Run(() =>
121             {
122                 try
123                 {
124                     ulong size = 0;
125                     Unmanaged.Run(Handle, out size).ThrowIfFailed("Failed to encode given image");
126
127                     byte[] buf = new byte[size];
128                     Marshal.Copy(outBuffer, buf, 0, (int)size);
129                     outStream.Write(buf, 0, (int)size);
130
131                     tcs.TrySetResult(true);
132                 }
133                 catch (Exception e)
134                 {
135                     tcs.TrySetException(e);
136                 }
137                 finally
138                 {
139                     Interop.Libc.Free(outBuffer);
140                 }
141             });
142
143             return tcs.Task;
144         }
145
146         internal Task EncodeAsync(Action<ImageEncoderHandle> settingInputAction, Stream outStream)
147         {
148             Debug.Assert(settingInputAction != null);
149
150             if (outStream == null)
151             {
152                 throw new ArgumentNullException(nameof(outStream));
153             }
154
155             if (outStream.CanWrite == false)
156             {
157                 throw new ArgumentException("The stream is not writable.", nameof(outStream));
158             }
159
160             Initialize();
161
162             settingInputAction(Handle);
163
164             return Run(outStream);
165         }
166
167         /// <summary>
168         /// Encodes an image from a raw image buffer to a specified <see cref="Stream"/>.
169         /// </summary>
170         /// <param name="inputBuffer">The image buffer to encode.</param>
171         /// <param name="outStream">The stream that the image is encoded to.</param>
172         /// <returns>A task that represents the asynchronous encoding operation.</returns>
173         /// <exception cref="ArgumentNullException">
174         ///     <paramref name="inputBuffer"/> is null.\n
175         ///     - or -\n
176         ///     <paramref name="outStream"/> is null.
177         /// </exception>
178         /// <exception cref="ArgumentException">
179         ///     <paramref name="inputBuffer"/> is an empty array.\n
180         ///     - or -\n
181         ///     <paramref name="outStream"/> is not writable.\n
182         /// </exception>
183         /// <exception cref="InvalidOperationException">The resolution is not set.</exception>
184         /// <exception cref="ObjectDisposedException">The <see cref="ImageEncoder"/> has already been disposed of.</exception>
185         /// <seealso cref="SetResolution"/>
186         public Task EncodeAsync(byte[] inputBuffer, Stream outStream)
187         {
188             if (inputBuffer == null)
189             {
190                 throw new ArgumentNullException(nameof(inputBuffer));
191             }
192
193             if (inputBuffer.Length == 0)
194             {
195                 throw new ArgumentException("buffer is empty.", nameof(inputBuffer));
196             }
197
198             return EncodeAsync(handle =>
199             {
200                 Unmanaged.SetInputBuffer(handle, inputBuffer).
201                         ThrowIfFailed("Failed to configure encoder; InputBuffer");
202             }, outStream);
203         }
204
205         internal void Initialize()
206         {
207             Configure(Handle);
208
209             if (_hasResolution == false)
210             {
211                 throw new InvalidOperationException("Resolution is not set.");
212             }
213         }
214
215         internal abstract void Configure(ImageEncoderHandle handle);
216
217         #region IDisposable Support
218         private bool _disposed = false;
219
220         /// <summary>
221         /// Releases the unmanaged resources used by the ImageEncoder.
222         /// </summary>
223         /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
224         protected virtual void Dispose(bool disposing)
225         {
226             if (!_disposed)
227             {
228                 if (_handle != null)
229                 {
230                     _handle.Dispose();
231                 }
232                 _disposed = true;
233             }
234         }
235
236         /// <summary>
237         /// Releases all resources used by the ImageEncoder.
238         /// </summary>
239         public void Dispose()
240         {
241             Dispose(true);
242         }
243         #endregion
244     }
245
246     /// <summary>
247     /// Provides the ability to encode the Bitmap (BMP) format images.
248     /// </summary>
249     public class BmpEncoder : ImageEncoder
250     {
251         /// <summary>
252         /// Initializes a new instance of the <see cref="BmpEncoder"/> class.
253         /// </summary>
254         /// <remarks><see cref="ImageEncoder.OutputFormat"/> will be the <see cref="ImageFormat.Bmp"/>.</remarks>
255         public BmpEncoder() : base(ImageFormat.Bmp)
256         {
257         }
258
259         internal override void Configure(ImageEncoderHandle handle)
260         {
261         }
262     }
263
264     /// <summary>
265     /// Provides the ability to encode the Portable Network Graphics (PNG) format images.
266     /// </summary>
267     public class PngEncoder : ImageEncoder
268     {
269         /// <summary>
270         /// A read-only field that represents the default value of <see cref="Compression"/>.
271         /// </summary>
272         public static readonly PngCompression DefaultCompression = PngCompression.Level6;
273
274         private PngCompression? _compression;
275
276         /// <summary>
277         /// Initializes a new instance of the <see cref="PngEncoder"/> class.
278         /// </summary>
279         /// <remarks><see cref="ImageEncoder.OutputFormat"/> will be the <see cref="ImageFormat.Png"/>.</remarks>
280         public PngEncoder() :
281             base(ImageFormat.Png)
282         {
283         }
284
285         /// <summary>
286         /// Initializes a new instance of the <see cref="PngEncoder"/> class with <see cref="PngCompression"/>.
287         /// </summary>
288         /// <remarks><see cref="ImageEncoder.OutputFormat"/> will be the <see cref="ImageFormat.Png"/>.</remarks>
289         /// <param name="compression">The compression level of the encoder.</param>
290         /// <exception cref="ArgumentException"><paramref name="compression"/> is invalid.</exception>
291         public PngEncoder(PngCompression compression) :
292             base(ImageFormat.Png)
293         {
294             Compression = compression;
295         }
296
297         /// <summary>
298         /// Gets or sets the compression level of the png image.
299         /// </summary>
300         /// <value>The compression level. The default is <see cref="PngCompression.Level6"/>.</value>
301         /// <exception cref="ArgumentException"><paramref name="value"/> is invalid.</exception>
302         public PngCompression Compression
303         {
304             get { return _compression ?? DefaultCompression; }
305             set
306             {
307                 ValidationUtil.ValidateEnum(typeof(PngCompression), value, nameof(Compression));
308
309                 _compression = value;
310             }
311         }
312
313         internal override void Configure(ImageEncoderHandle handle)
314         {
315             if (_compression.HasValue)
316             {
317                 Unmanaged.SetPngCompression(handle, _compression.Value).
318                     ThrowIfFailed("Failed to configure encoder; PngCompression");
319             }
320         }
321     }
322
323     /// <summary>
324     /// Provides the ability to encode the Joint Photographic Experts Group (JPEG) format images.
325     /// </summary>
326     public class JpegEncoder : ImageEncoder
327     {
328         /// <summary>
329         /// A read-only field that represents the default value of <see cref="Quality"/>.
330         /// </summary>
331         public static readonly int DefaultQuality = 75;
332
333         private int? _quality;
334
335         /// <summary>
336         /// Initializes a new instance of the <see cref="JpegEncoder"/> class.
337         /// </summary>
338         /// <remarks><see cref="ImageEncoder.OutputFormat"/> will be the <see cref="ImageFormat.Jpeg"/>.</remarks>
339         public JpegEncoder() : base(ImageFormat.Jpeg)
340         {
341         }
342
343
344         /// <summary>
345         /// Initializes a new instance of the <see cref="JpegEncoder"/> class with initial quality value.
346         /// </summary>
347         /// <param name="quality">The quality for JPEG image encoding; from 1(lowest quality) to 100(highest quality).</param>
348         /// <remarks><see cref="ImageEncoder.OutputFormat"/> will be the <see cref="ImageFormat.Jpeg"/>.</remarks>
349         /// <exception cref="ArgumentOutOfRangeException">
350         ///     <paramref name="quality"/> is less than or equal to 0.\n
351         ///     - or -\n
352         ///     <paramref name="quality"/> is greater than 100.
353         /// </exception>
354         public JpegEncoder(int quality) :
355             base(ImageFormat.Jpeg)
356         {
357             Quality = quality;
358         }
359
360         /// <summary>
361         /// Gets or sets the quality of the encoded image.
362         /// </summary>
363         /// <value>
364         /// The quality of the output image. The default is 75.\n
365         /// Valid value is from 1(lowest quality) to 100(highest quality).
366         /// </value>
367         /// <exception cref="ArgumentOutOfRangeException">
368         ///     <paramref name="value"/> is less than or equal to 0.\n
369         ///     - or -\n
370         ///     <paramref name="value"/> is greater than 100.
371         /// </exception>
372         public int Quality
373         {
374             get { return _quality ?? DefaultQuality; }
375             set
376             {
377                 if (value <= 0 || value > 100)
378                 {
379                     throw new ArgumentOutOfRangeException(nameof(Quality), value,
380                         "Valid range is from 1 to 100, inclusive.");
381                 }
382                 _quality = value;
383             }
384         }
385
386         internal override void Configure(ImageEncoderHandle handle)
387         {
388             if (_quality.HasValue)
389             {
390                 Unmanaged.SetQuality(handle, _quality.Value).
391                     ThrowIfFailed("Failed to configure encoder; Quality");
392             }
393         }
394     }
395
396     /// <summary>
397     /// Provides the ability to encode the Graphics Interchange Format (GIF) format images.
398     /// </summary>
399     public class GifEncoder : ImageEncoder
400     {
401         /// <summary>
402         /// Initializes a new instance of the <see cref="GifEncoder"/> class.
403         /// </summary>
404         /// <remarks><see cref="ImageEncoder.OutputFormat"/> will be the <see cref="ImageFormat.Gif"/>.</remarks>
405         public GifEncoder() : base(ImageFormat.Gif)
406         {
407         }
408
409         internal override void Configure(ImageEncoderHandle handle)
410         {
411         }
412
413         /// <summary>
414         /// Encodes a Graphics Interchange Format (GIF) image from multiple raw image buffers to a specified <see cref="Stream"/>.
415         /// </summary>
416         /// <param name="frames">The image frames to encode.</param>
417         /// <param name="outStream">The stream that the image is encoded to.</param>
418         /// <returns>A task that represents the asynchronous encoding operation.</returns>
419         /// <exception cref="ArgumentNullException">
420         ///     <paramref name="frames"/> is null.\n
421         ///     - or -\n
422         ///     <paramref name="outStream"/> is null.
423         /// </exception>
424         /// <exception cref="ArgumentException">
425         ///     <paramref name="frames"/> has no element(empty).\n
426         ///     - or -\n
427         ///     <paramref name="outStream"/> is not writable.\n
428         /// </exception>
429         /// <exception cref="InvalidOperationException">The resolution is not set.</exception>
430         /// <exception cref="ObjectDisposedException">The <see cref="ImageEncoder"/> has already been disposed of.</exception>
431         /// <seealso cref="ImageEncoder.SetResolution"/>
432         public Task EncodeAsync(IEnumerable<GifFrame> frames, Stream outStream)
433         {
434             if (frames == null)
435             {
436                 throw new ArgumentNullException(nameof(frames));
437             }
438
439             if (frames.Count() == 0)
440             {
441                 throw new ArgumentException("frames is a empty collection", nameof(frames));
442             }
443
444             return EncodeAsync(handle =>
445             {
446                 foreach (GifFrame frame in frames)
447                 {
448                     if (frame == null)
449                     {
450                         throw new ArgumentNullException(nameof(frames));
451                     }
452                     Unmanaged.SetInputBuffer(handle, frame.Buffer).
453                         ThrowIfFailed("Failed to configure encoder; Buffer");
454
455                     Unmanaged.SetGifFrameDelayTime(handle, (ulong)frame.Delay).
456                         ThrowIfFailed("Failed to configure encoder; Delay");
457                 }
458             }, outStream);
459         }
460     }
461
462 }