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