[ImageUtil] Remove deprecated API and change native pinvoke APIs (#5950)
[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;
24 using System.Threading.Tasks;
25 using static Interop;
26 using NativeEncoder = Interop.ImageUtil.Encode;
27 using NativeUtil = Interop.ImageUtil;
28
29 namespace Tizen.Multimedia.Util
30 {
31     /// <summary>
32     /// This is a base class for image encoders.
33     /// </summary>
34     /// <since_tizen> 4 </since_tizen>
35     public abstract class ImageEncoder : IDisposable
36     {
37         private ImageEncoderHandle _handle;
38         internal IntPtr _imageUtilHandle;
39         internal IntPtr _animationHandle;
40
41         internal Size? _resolution;
42         internal ColorSpace? _colorSpace;
43
44         internal ImageEncoder(ImageFormat format)
45         {
46             NativeEncoder.Create(format, out _handle).ThrowIfFailed("Failed to create ImageEncoder");
47
48             Debug.Assert(_handle != null);
49
50             OutputFormat = format;
51         }
52
53         private ImageEncoderHandle Handle
54         {
55             get
56             {
57                 if (_disposed)
58                 {
59                     throw new ObjectDisposedException(GetType().Name);
60                 }
61
62                 return _handle;
63             }
64         }
65
66         /// <summary>
67         /// Gets the image format of this encoder.
68         /// </summary>
69         /// <since_tizen> 4 </since_tizen>
70         public ImageFormat OutputFormat { get; }
71
72         /// <summary>
73         /// Sets the resolution of the output image.
74         /// </summary>
75         /// <param name="resolution">The target resolution.</param>
76         /// <exception cref="ArgumentOutOfRangeException">
77         ///     The width of <paramref name="resolution"/> is less than or equal to zero.<br/>
78         ///     -or-<br/>
79         ///     The height of <paramref name="resolution"/> is less than or equal to zero.
80         /// </exception>
81         /// <since_tizen> 4 </since_tizen>
82         public void SetResolution(Size resolution)
83         {
84             if (resolution.Width <= 0)
85             {
86                 throw new ArgumentOutOfRangeException(nameof(resolution), resolution.Width,
87                     "The width of resolution can't be less than or equal to zero.");
88             }
89             if (resolution.Height <= 0)
90             {
91                 throw new ArgumentOutOfRangeException(nameof(resolution), resolution.Height,
92                     "The height of resolution can't be less than or equal to zero.");
93             }
94
95             _resolution = resolution;
96         }
97
98         /// <summary>
99         /// Sets the color-space of the output image.
100         /// </summary>
101         /// <param name="colorSpace">The target color-space.</param>
102         /// <value>The default color space is <see cref="ColorSpace.Rgba8888"/>
103         /// <exception cref="ArgumentException"><paramref name="colorSpace"/> is invalid.</exception>
104         /// <exception cref="NotSupportedException"><paramref name="colorSpace"/> is not supported by the encoder.</exception>
105         /// <seealso cref="ImageUtil.GetSupportedColorSpaces(ImageFormat)"/>
106         /// <since_tizen> 4 </since_tizen>
107         public void SetColorSpace(ColorSpace colorSpace)
108         {
109             ValidationUtil.ValidateEnum(typeof(ColorSpace), colorSpace, nameof(colorSpace));
110
111             if (ImageUtil.GetSupportedColorSpaces(OutputFormat).Contains(colorSpace) == false)
112             {
113                 throw new NotSupportedException($"{colorSpace.ToString()} is not supported for {OutputFormat}.");
114             }
115
116             _colorSpace = colorSpace;
117         }
118
119         internal virtual void RunEncoding(object outStream)
120         {
121             IntPtr outBuffer = IntPtr.Zero;
122             int size = 0;
123
124             try
125             {
126                 NativeEncoder.RunToBuffer(Handle, _imageUtilHandle, out outBuffer, out size).
127                     ThrowIfFailed("Failed to encode given image");
128
129                 byte[] buf = new byte[size];
130                 Marshal.Copy(outBuffer, buf, 0, (int)size);
131                 (outStream as Stream).Write(buf, 0, (int)size);
132             }
133             finally
134             {
135                 Marshal.FreeHGlobal(outBuffer);
136                 NativeUtil.Destroy(_imageUtilHandle).ThrowIfFailed("Failed to destroy ImageUtil handle");
137             }
138         }
139
140         private Task Run(Stream outStream)
141         {
142             return Task.Factory.StartNew(RunEncoding, outStream, CancellationToken.None,
143                 TaskCreationOptions.DenyChildAttach | TaskCreationOptions.LongRunning,
144                 TaskScheduler.Default);
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         /// <since_tizen> 4 </since_tizen>
188         public Task EncodeAsync(byte[] inputBuffer, Stream outStream)
189         {
190             if (inputBuffer == null)
191             {
192                 throw new ArgumentNullException(nameof(inputBuffer));
193             }
194
195             if (inputBuffer.Length == 0)
196             {
197                 throw new ArgumentException("buffer is empty.", nameof(inputBuffer));
198             }
199
200             return EncodeAsync(handle => {
201                 NativeUtil.Create((uint)_resolution.Value.Width, (uint)_resolution.Value.Height, _colorSpace.Value.ToImageColorSpace(),
202                     inputBuffer, inputBuffer.Length, out _imageUtilHandle).ThrowIfFailed("Failed to create ImageUtil handle");
203             }, outStream);
204         }
205
206         internal void Initialize()
207         {
208             Configure(Handle);
209
210             if (!_resolution.HasValue)
211             {
212                 throw new InvalidOperationException("Resolution is not set.");
213             }
214             if (!_colorSpace.HasValue)
215             {
216                 _colorSpace = ColorSpace.Rgba8888;
217                 Log.Info("Tizen.Multimedia.Util", "ColorSpace was set to default value(Rgba8888).");
218             }
219         }
220
221         internal abstract void Configure(ImageEncoderHandle handle);
222
223         #region IDisposable Support
224         private bool _disposed = false;
225
226         /// <summary>
227         /// Releases the unmanaged resources used by the ImageEncoder.
228         /// </summary>
229         /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
230         /// <since_tizen> 4 </since_tizen>
231         protected virtual void Dispose(bool disposing)
232         {
233             if (!_disposed)
234             {
235                 if (_handle != null)
236                 {
237                     _handle.Dispose();
238                 }
239                 _disposed = true;
240             }
241         }
242
243         /// <summary>
244         /// Releases all resources used by the ImageEncoder.
245         /// </summary>
246         /// <since_tizen> 4 </since_tizen>
247         public void Dispose()
248         {
249             Dispose(true);
250         }
251         #endregion
252     }
253
254     /// <summary>
255     /// Provides the ability to encode the Bitmap (BMP) format images.
256     /// </summary>
257     /// <since_tizen> 4 </since_tizen>
258     public class BmpEncoder : ImageEncoder
259     {
260         /// <summary>
261         /// Initializes a new instance of the <see cref="BmpEncoder"/> class.
262         /// </summary>
263         /// <remarks><see cref="ImageEncoder.OutputFormat"/> will be the <see cref="ImageFormat.Bmp"/>.</remarks>
264         /// <since_tizen> 4 </since_tizen>
265         public BmpEncoder() : base(ImageFormat.Bmp)
266         {
267         }
268
269         internal override void Configure(ImageEncoderHandle handle)
270         {
271         }
272     }
273
274     /// <summary>
275     /// Provides the ability to encode the Portable Network Graphics (PNG) format images.
276     /// </summary>
277     /// <since_tizen> 4 </since_tizen>
278     public class PngEncoder : ImageEncoder
279     {
280         /// <summary>
281         /// A read-only field that represents the default value of <see cref="Compression"/>.
282         /// </summary>
283         /// <since_tizen> 4 </since_tizen>
284         public static readonly PngCompression DefaultCompression = PngCompression.Level6;
285
286         private PngCompression? _compression;
287
288         /// <summary>
289         /// Initializes a new instance of the <see cref="PngEncoder"/> class.
290         /// </summary>
291         /// <remarks><see cref="ImageEncoder.OutputFormat"/> will be the <see cref="ImageFormat.Png"/>.</remarks>
292         /// <since_tizen> 4 </since_tizen>
293         public PngEncoder() :
294             base(ImageFormat.Png)
295         {
296         }
297
298         /// <summary>
299         /// Initializes a new instance of the <see cref="PngEncoder"/> class with <see cref="PngCompression"/>.
300         /// </summary>
301         /// <remarks><see cref="ImageEncoder.OutputFormat"/> will be the <see cref="ImageFormat.Png"/>.</remarks>
302         /// <param name="compression">The compression level of the encoder.</param>
303         /// <exception cref="ArgumentException"><paramref name="compression"/> is invalid.</exception>
304         /// <since_tizen> 4 </since_tizen>
305         public PngEncoder(PngCompression compression) :
306             base(ImageFormat.Png)
307         {
308             Compression = compression;
309         }
310
311         /// <summary>
312         /// Gets or sets the compression level of the png image.
313         /// </summary>
314         /// <value>The compression level. The default is <see cref="PngCompression.Level6"/>.</value>
315         /// <exception cref="ArgumentException"><paramref name="value"/> is invalid.</exception>
316         /// <since_tizen> 4 </since_tizen>
317         public PngCompression Compression
318         {
319             get { return _compression ?? DefaultCompression; }
320             set
321             {
322                 ValidationUtil.ValidateEnum(typeof(PngCompression), value, nameof(Compression));
323
324                 _compression = value;
325             }
326         }
327
328         internal override void Configure(ImageEncoderHandle handle)
329         {
330             if (_compression.HasValue)
331             {
332                 NativeEncoder.SetPngCompression(handle, _compression.Value).
333                     ThrowIfFailed("Failed to configure encoder; PngCompression");
334             }
335         }
336     }
337
338     /// <summary>
339     /// Provides the ability to encode the Joint Photographic Experts Group (JPEG) format images.
340     /// </summary>
341     /// <since_tizen> 4 </since_tizen>
342     public class JpegEncoder : ImageEncoder
343     {
344         /// <summary>
345         /// A read-only field that represents the default value of <see cref="Quality"/>.
346         /// </summary>
347         /// <since_tizen> 4 </since_tizen>
348         public static readonly int DefaultQuality = 75;
349
350         private int? _quality;
351
352         /// <summary>
353         /// Initializes a new instance of the <see cref="JpegEncoder"/> class.
354         /// </summary>
355         /// <remarks><see cref="ImageEncoder.OutputFormat"/> will be the <see cref="ImageFormat.Jpeg"/>.</remarks>
356         /// <since_tizen> 4 </since_tizen>
357         public JpegEncoder() : base(ImageFormat.Jpeg)
358         {
359         }
360
361
362         /// <summary>
363         /// Initializes a new instance of the <see cref="JpegEncoder"/> class with initial quality value.
364         /// </summary>
365         /// <param name="quality">The quality for JPEG image encoding; from 1(lowest quality) to 100(highest quality).</param>
366         /// <remarks><see cref="ImageEncoder.OutputFormat"/> will be the <see cref="ImageFormat.Jpeg"/>.</remarks>
367         /// <exception cref="ArgumentOutOfRangeException">
368         ///     <paramref name="quality"/> is less than or equal to 0.<br/>
369         ///     -or-<br/>
370         ///     <paramref name="quality"/> is greater than 100.
371         /// </exception>
372         /// <since_tizen> 4 </since_tizen>
373         public JpegEncoder(int quality) :
374             base(ImageFormat.Jpeg)
375         {
376             Quality = quality;
377         }
378
379         /// <summary>
380         /// Gets or sets the quality of the encoded image.
381         /// </summary>
382         /// <value>
383         /// The quality of the output image. The default is 75.<br/>
384         /// Valid value is from 1(lowest quality) to 100(highest quality).
385         /// </value>
386         /// <exception cref="ArgumentOutOfRangeException">
387         ///     <paramref name="value"/> is less than or equal to 0.<br/>
388         ///     -or-<br/>
389         ///     <paramref name="value"/> is greater than 100.
390         /// </exception>
391         /// <since_tizen> 4 </since_tizen>
392         public int Quality
393         {
394             get { return _quality ?? DefaultQuality; }
395             set
396             {
397                 if (value <= 0 || value > 100)
398                 {
399                     throw new ArgumentOutOfRangeException(nameof(Quality), value,
400                         "Valid range is from 1 to 100, inclusive.");
401                 }
402                 _quality = value;
403             }
404         }
405
406         internal override void Configure(ImageEncoderHandle handle)
407         {
408             if (_quality.HasValue)
409             {
410                 NativeEncoder.SetQuality(handle, _quality.Value).
411                     ThrowIfFailed("Failed to configure encoder; Quality");
412             }
413         }
414     }
415
416     /// <summary>
417     /// Provides the ability to encode the Graphics Interchange Format (GIF) format images.
418     /// </summary>
419     /// <since_tizen> 4 </since_tizen>
420     public class GifEncoder : ImageEncoder
421     {
422         private IEnumerable<GifFrame> _frames;
423
424         /// <summary>
425         /// Initializes a new instance of the <see cref="GifEncoder"/> class.
426         /// </summary>
427         /// <remarks><see cref="ImageEncoder.OutputFormat"/> will be the <see cref="ImageFormat.Gif"/>.</remarks>
428         /// <since_tizen> 4 </since_tizen>
429         public GifEncoder() : base(ImageFormat.Gif)
430         {
431         }
432
433         internal override void Configure(ImageEncoderHandle handle)
434         {
435         }
436
437         /// <summary>
438         /// Encodes a Graphics Interchange Format (GIF) image from multiple raw image buffers to a specified <see cref="Stream"/>.
439         /// </summary>
440         /// <param name="frames">The image frames to encode.</param>
441         /// <param name="outStream">The stream that the image is encoded to.</param>
442         /// <returns>A task that represents the asynchronous encoding operation.</returns>
443         /// <exception cref="ArgumentNullException">
444         ///     <paramref name="frames"/> is null.<br/>
445         ///     -or-<br/>
446         ///     <paramref name="outStream"/> is null.
447         /// </exception>
448         /// <exception cref="ArgumentException">
449         ///     <paramref name="frames"/> has no element(empty).<br/>
450         ///     -or-<br/>
451         ///     <paramref name="outStream"/> is not writable.
452         /// </exception>
453         /// <exception cref="InvalidOperationException">The resolution is not set.</exception>
454         /// <exception cref="ObjectDisposedException">The <see cref="ImageEncoder"/> has already been disposed of.</exception>
455         /// <seealso cref="ImageEncoder.SetResolution"/>
456         /// <since_tizen> 4 </since_tizen>
457         public Task EncodeAsync(IEnumerable<GifFrame> frames, Stream outStream)
458         {
459             if (frames == null)
460             {
461                 throw new ArgumentNullException(nameof(frames));
462             }
463
464             if (!frames.Any())
465             {
466                 throw new ArgumentException("frames is a empty collection", nameof(frames));
467             }
468
469             _frames = frames;
470
471             return EncodeAsync(handle => {}, outStream);
472         }
473
474         internal override void RunEncoding(object outStream)
475         {
476             IntPtr outBuffer = IntPtr.Zero;
477
478             NativeEncoder.AnimationCreate(AnimationType.Gif, out _animationHandle).
479                 ThrowIfFailed("Failed to create animation handle");
480
481             try
482             {
483                 foreach (GifFrame frame in _frames)
484                 {
485                     if (frame == null)
486                     {
487                         throw new ArgumentNullException(nameof(frame));
488                     }
489
490                     NativeUtil.Create((uint)_resolution.Value.Width, (uint)_resolution.Value.Height, _colorSpace.Value.ToImageColorSpace(),
491                         frame.Buffer, frame.Buffer.Length, out _imageUtilHandle).ThrowIfFailed("Failed to create ImageUtil handle");
492                     NativeEncoder.AnimationAddFrame(_animationHandle, _imageUtilHandle, frame.Delay).
493                         ThrowIfFailed("Failed to add frame");
494
495                     NativeUtil.Destroy(_imageUtilHandle).ThrowIfFailed("Failed to destroy ImageUtil handle");
496                 }
497
498                 NativeEncoder.AnimationSaveToBuffer(_animationHandle, out outBuffer, out ulong size).
499                     ThrowIfFailed("Failed to encode given image");
500
501                 byte[] buf = new byte[size];
502                 Marshal.Copy(outBuffer, buf, 0, (int)size);
503                 (outStream as Stream).Write(buf, 0, (int)size);
504             }
505             finally
506             {
507                 Marshal.FreeHGlobal(outBuffer);
508                 if (_animationHandle != IntPtr.Zero)
509                 {
510                     NativeEncoder.AnimationDestroy(_animationHandle).ThrowIfFailed("Failed to destroy animation handle");
511                 }
512             }
513         }
514     }
515
516     /// <summary>
517     /// Provides the ability to encode the WebP (Lossless and lossy compression for images on the web) format images.
518     /// </summary>
519     /// <since_tizen> 8 </since_tizen>
520     public class WebPEncoder : ImageEncoder
521     {
522         /// <summary>
523         /// Initializes a new instance of the <see cref="WebPEncoder"/> class.
524         /// </summary>
525         /// <remarks><see cref="ImageEncoder.OutputFormat"/> will be the <see cref="ImageFormat.WebP"/>.</remarks>
526         /// <since_tizen> 8 </since_tizen>
527         public WebPEncoder() :
528             base(ImageFormat.WebP)
529         {
530         }
531
532         /// <summary>
533         /// Initializes a new instance of the <see cref="WebPEncoder"/> class with the information for lossless or lossy compression.
534         /// </summary>
535         /// <remarks><see cref="ImageEncoder.OutputFormat"/> will be the <see cref="ImageFormat.WebP"/>.</remarks>
536         /// <param name="lossless">
537         /// The flag determining whether the compression is lossless or lossy: true for lossless, false for lossy.<br/>
538         /// The default value is false.
539         /// </param>
540         /// <since_tizen> 8 </since_tizen>
541         public WebPEncoder(bool lossless) :
542             base(ImageFormat.WebP)
543         {
544             Lossless = lossless;
545         }
546
547         /// <summary>
548         /// Gets or sets the lossless or lossy WebP compression.
549         /// </summary>
550         /// <value>
551         /// The property determining whether the WebP compression is lossless or lossy.<br/>
552         /// The default is false(lossy).</value>
553         /// <since_tizen> 8 </since_tizen>
554         public bool Lossless { get; set; } = false;
555
556         internal override void Configure(ImageEncoderHandle handle)
557         {
558             NativeEncoder.SetLossless(handle, Lossless).
559                 ThrowIfFailed("Failed to configure encoder; Lossless");
560         }
561     }
562
563     /// <summary>
564     /// Provides the ability to encode the JPEG(Joint Photographic Experts Group) XL format images.
565     /// </summary>
566     /// <since_tizen> 10 </since_tizen>
567     public class JpegXlEncoder : ImageEncoder
568     {
569         /// <summary>
570         /// Initializes a new instance of the <see cref="JpegXlEncoder"/> class.
571         /// </summary>
572         /// <remarks><see cref="ImageEncoder.OutputFormat"/> will be the <see cref="ImageFormat.JpegXl"/>.</remarks>
573         /// <since_tizen> 10 </since_tizen>
574         public JpegXlEncoder() :
575             base(ImageFormat.JpegXl)
576         {
577         }
578
579         /// <summary>
580         /// Initializes a new instance of the <see cref="JpegXlEncoder"/> class for lossless or lossy compression.
581         /// </summary>
582         /// <remarks><see cref="ImageEncoder.OutputFormat"/> will be the <see cref="ImageFormat.JpegXl"/>.</remarks>
583         /// <param name="lossless">
584         /// The flag determining whether the compression is lossless or lossy: true for lossless, false for lossy.<br/>
585         /// The default value is false.
586         /// </param>
587         /// <since_tizen> 10 </since_tizen>
588         public JpegXlEncoder(bool lossless) :
589             base(ImageFormat.JpegXl)
590         {
591             Lossless = lossless;
592         }
593
594         /// <summary>
595         /// Gets or sets the lossless or lossy JpegXl compression.
596         /// </summary>
597         /// <value>
598         /// The property determining whether the JpegXl compression is lossless or lossy.<br/>
599         /// The default is false(lossy).</value>
600         /// <since_tizen> 10 </since_tizen>
601         public bool Lossless { get; set; } = false;
602
603         internal override void Configure(ImageEncoderHandle handle)
604         {
605             NativeEncoder.SetLossless(handle, Lossless).
606                 ThrowIfFailed($"Failed to configure encoder; Lossless={Lossless}");
607         }
608     }
609 }