Release 4.0.0-preview1-00201
[platform/core/csapi/tizenfx.git] / src / Tizen.Multimedia.Util / ImageUtil / ImageTransform.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;
19 using System.Collections.Generic;
20 using System.Diagnostics;
21 using System.Runtime.InteropServices;
22 using System.Threading.Tasks;
23 using static Interop.ImageUtil;
24 using static Interop.ImageUtil.Transform;
25
26 namespace Tizen.Multimedia.Util
27 {
28     /// <summary>
29     /// A base class for image transformations.
30     /// </summary>
31     public abstract class ImageTransform
32     {
33         internal ImageTransform()
34         {
35         }
36
37         internal async Task<MediaPacket> RunAsync(TransformHandle handle, MediaPacket source)
38         {
39             var tcs = new TaskCompletionSource<MediaPacket>();
40
41             TransformCompletedCallback cb = (nativehandle, errorCode, _) =>
42             {
43                 if (errorCode == ImageUtilError.None)
44                 {
45                     try
46                     {
47                         tcs.TrySetResult(MediaPacket.From(Marshal.PtrToStructure<IntPtr>(nativehandle)));
48                     }
49                     catch (Exception e)
50                     {
51                         tcs.TrySetException(e);
52                     }
53                 }
54                 else
55                 {
56                     tcs.TrySetException(errorCode.ToException("Image transformation failed"));
57                 }
58             };
59
60             using (var cbKeeper = ObjectKeeper.Get(cb))
61             {
62                 Run(handle, source.GetHandle(), cb).ThrowIfFailed("Failed to transform given packet with " + GetType());
63
64                 return await tcs.Task;
65             }
66         }
67
68         internal static TransformHandle CreateHandle()
69         {
70             Create(out var handle).ThrowIfFailed("Failed to run ImageTransformer");
71             Debug.Assert(handle != null);
72             return handle;
73         }
74
75         internal abstract void Configure(TransformHandle handle);
76
77         internal virtual Task<MediaPacket> ApplyAsync(MediaPacket source)
78         {
79             using (TransformHandle handle = CreateHandle())
80             {
81                 Configure(handle);
82
83                 return RunAsync(handle, source);
84             }
85         }
86     }
87
88     /// <summary>
89     /// Represents a collection of <see cref="ImageTransform"/> objects that can be individually accessed by index.
90     /// </summary>
91     public class ImageTransformCollection : IEnumerable<ImageTransform>, IList<ImageTransform>
92     {
93         private List<ImageTransform> _list = new List<ImageTransform>();
94
95         /// <summary>
96         /// Initializes a new instance of the ImageTransformCollection class.
97         /// </summary>
98         public ImageTransformCollection()
99         {
100         }
101
102         /// <summary>
103         /// Gets or sets the <see cref="ImageTransform"/> at the specified index.
104         /// </summary>
105         /// <param name="index">The zero-based index of the <see cref="ImageTransform"/> to get or set.</param>
106         /// <value>The <see cref="ImageTransform"/> at the specified index.</value>
107         /// <exception cref="ArgumentOutOfRangeException">
108         ///     index is less than 0.\n
109         ///     - or -\n
110         ///     index is equal to or greater than Count.
111         /// </exception>
112         public ImageTransform this[int index]
113         {
114             get { return _list[index]; }
115             set { _list[index] = value; }
116         }
117
118         /// <summary>
119         /// Gets the number of items contained in the TransformCollection.
120         /// </summary>
121         public int Count => _list.Count;
122
123         bool ICollection<ImageTransform>.IsReadOnly => false;
124
125         /// <summary>
126         /// Adds a <see cref="ImageTransform"/> to the end of the collection.
127         /// </summary>
128         /// <param name="item">The <see cref="ImageTransform"/> to add.</param>
129         /// <remarks>
130         /// <see cref="ImageTransformCollection"/> accepts null as a valid value for reference types and allows duplicate elements.
131         /// </remarks>
132         public void Add(ImageTransform item)
133         {
134             if (item == null)
135             {
136                 throw new ArgumentNullException(nameof(item));
137             }
138             _list.Add(item);
139         }
140
141         /// <summary>
142         /// Removes all items.
143         /// </summary>
144         public void Clear() => _list.Clear();
145
146         /// <summary>
147         /// Determines whether the <see cref="ImageTransformCollection"/> contains the specified item.
148         /// </summary>
149         /// <param name="item">The <see cref="ImageTransform"/> to locate in the collection.</param>
150         /// <returns>true if the <see cref="ImageTransform"/> is found in the collection; otherwise, false.</returns>
151         public bool Contains(ImageTransform item) => _list.Contains(item);
152
153         /// <summary>
154         /// Copies the items of the collection to an array, starting at the specified array index.
155         /// </summary>
156         /// <param name="array">The one-dimensional array that is the destination of the items copied from the collection.</param>
157         /// <param name="arrayIndex">The zero-based index in array at which copying begins.</param>
158         /// <exception cref="ArgumentNullException"><paramref name="array"/> is null.</exception>
159         /// <exception cref="ArgumentOutOfRangeException"><paramref name="arrayIndex"/> is less than 0.</exception>
160         /// <exception cref="ArgumentException">
161         /// The number of elements in the source collection is greater than the available space from arrayIndex to the end of the destination array.
162         /// </exception>
163         public void CopyTo(ImageTransform[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex);
164
165         /// <summary>
166         /// Determines the index of the specified item in the collection.
167         /// </summary>
168         /// <param name="item">The <see cref="ImageTransform"/> to locate in the collection.</param>
169         /// <returns>The index of value if found in the <see cref="ImageTransformCollection"/>; otherwise, -1.</returns>
170         public int IndexOf(ImageTransform item) => _list.IndexOf(item);
171
172         /// <summary>
173         /// Inserts a <see cref="ImageTransform"/> into the collection at the specified index.
174         /// </summary>
175         /// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param>
176         /// <param name="item">The <see cref="ImageTransform"/> to insert into the collection.</param>
177         /// <exception cref="ArgumentNullException"><paramref name="item"/> is null.</exception>
178         /// <exception cref="ArgumentOutOfRangeException">
179         ///     index is less than 0.\n
180         ///     -or-\n
181         ///     index is greater than <see cref="Count"/>.
182         /// </exception>
183         public void Insert(int index, ImageTransform item)
184         {
185             if (item == null)
186             {
187                 throw new ArgumentNullException(nameof(item));
188             }
189             _list.Insert(index, item);
190         }
191
192         /// <summary>
193         /// Removes the first occurrence of the specified <see cref="ImageTransform"/> from the collection.
194         /// </summary>
195         /// <param name="item">The <see cref="ImageTransform"/> to remove.</param>
196         /// <returns>true if <paramref name="item"/> was removed from the collection; otherwise, false.</returns>
197         public bool Remove(ImageTransform item) => _list.Remove(item);
198
199         /// <summary>
200         /// Removes the <see cref="ImageTransform"/> at the specified index.
201         /// </summary>
202         /// <param name="index">The zero-based index to remove.</param>
203         /// <exception cref="ArgumentOutOfRangeException">
204         ///     index is less than 0.\n
205         ///     - or -\n
206         ///     index is equal to or greater than <see cref="Count"/>.
207         /// </exception>
208         public void RemoveAt(int index) => _list.RemoveAt(index);
209
210         /// <summary>
211         /// Returns an enumerator that can iterate through the collection.
212         /// </summary>
213         /// <returns>An enumerator that can be used to iterate through the collection.</returns>
214         public IEnumerator<ImageTransform> GetEnumerator() => _list.GetEnumerator();
215
216         IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator();
217     }
218
219     // TODO need to improve performance
220     /// <summary>
221     /// Represents a <see cref="ImageTransform"/> that is a composite of the transforms.
222     /// </summary>
223     public class ImageTransformGroup : ImageTransform
224     {
225         /// <summary>
226         /// Gets or sets the <see cref="ImageTransformCollection"/>.
227         /// </summary>
228         public ImageTransformCollection Children { get; set; }
229
230         /// <summary>
231         /// Initializes a new instance of the ImageTransformGroup class.
232         /// </summary>
233         public ImageTransformGroup()
234         {
235             Children = new ImageTransformCollection();
236         }
237
238         internal override void Configure(TransformHandle handle)
239         {
240             // intended blank
241         }
242
243         internal override async Task<MediaPacket> ApplyAsync(MediaPacket source)
244         {
245             if (Children.Count == 0)
246             {
247                 return source;
248             }
249
250             var items = Children;
251
252             MediaPacket curPacket = await items[0].ApplyAsync(source);
253
254             for (int i = 1; i < items.Count; ++i)
255             {
256                 var oldPacket = curPacket;
257                 try
258                 {
259                     curPacket = await items[i].ApplyAsync(curPacket);
260                 }
261                 finally
262                 {
263                     oldPacket.Dispose();
264                 }
265             }
266
267             return curPacket;
268         }
269     }
270
271     /// <summary>
272     /// Rotates an image.
273     /// </summary>
274     /// <seealso cref="Rotation"/>
275     public class RotateTransform : ImageTransform
276     {
277         private Rotation _rotation;
278
279         /// <summary>
280         /// Initializes a new instance of the <see cref="RotateTransform"/> class.
281         /// </summary>
282         /// <param name="rotation">The value how to rotate an image.</param>
283         /// <exception cref="ArgumentException"><paramref name="rotation"/> is invalid.</exception>
284         /// <exception cref="ArgumentOutOfRangeException"><paramref name="rotation"/> is <see cref="Rotation.Rotate90"/>.</exception>
285         public RotateTransform(Rotation rotation)
286         {
287             Rotation = rotation;
288
289         }
290
291         /// <summary>
292         /// Gets or sets the value how to rotate an image.
293         /// </summary>
294         /// <exception cref="ArgumentException"><paramref name="value"/> is invalid.</exception>
295         /// <exception cref="ArgumentOutOfRangeException"><paramref name="value"/> is <see cref="Rotation.Rotate90"/>.</exception>
296         public Rotation Rotation
297         {
298             get { return _rotation; }
299             set
300             {
301                 ValidationUtil.ValidateEnum(typeof(Rotation), value, nameof(Rotation));
302
303                 if (value == Rotation.Rotate0)
304                 {
305                     throw new ArgumentOutOfRangeException(nameof(value), "Rotation can't be Rotate0.");
306                 }
307
308                 _rotation = value;
309             }
310         }
311
312         internal override void Configure(TransformHandle handle)
313         {
314             SetRotation(handle, GetImageRotation());
315         }
316
317         private ImageRotation GetImageRotation()
318         {
319             switch (Rotation)
320             {
321                 case Rotation.Rotate90: return ImageRotation.Rotate90;
322                 case Rotation.Rotate180: return ImageRotation.Rotate180;
323                 case Rotation.Rotate270: return ImageRotation.Rotate270;
324             }
325
326             Debug.Fail("Rotation is invalid value!");
327             return ImageRotation.Rotate0;
328         }
329     }
330
331
332     /// <summary>
333     /// Flips an image.
334     /// </summary>
335     /// <seealso cref="Rotation"/>
336     public class FlipTransform : ImageTransform
337     {
338         private Flips _flip;
339
340         /// <summary>
341         /// Initializes a new instance of the <see cref="RotateTransform"/> class.
342         /// </summary>
343         /// <param name="flip">The value how to flip an image.</param>
344         /// <exception cref="ArgumentException"><paramref name="flip"/> is invalid.</exception>
345         /// <exception cref="ArgumentOutOfRangeException"><paramref name="flip"/> is <see cref="Flips.None"/>.</exception>
346         public FlipTransform(Flips flip)
347         {
348             Flip = flip;
349         }
350
351         /// <summary>
352         /// Gets or sets the value how to rotate an image.
353         /// </summary>
354         /// <exception cref="ArgumentException"><paramref name="value"/> is invalid.</exception>
355         /// <exception cref="ArgumentOutOfRangeException"><paramref name="value"/> is <see cref="Flips.None"/>.</exception>
356         public Flips Flip
357         {
358             get { return _flip; }
359             set
360             {
361                 ValidationUtil.ValidateFlagsEnum(value, Flips.Horizontal | Flips.Vertical, nameof(Flips));
362
363                 if (value == Flips.None)
364                 {
365                     throw new ArgumentOutOfRangeException(nameof(value), "Flip can't be None.");
366                 }
367
368                 _flip = value;
369             }
370         }
371
372         internal override void Configure(TransformHandle handle)
373         {
374             // intended blank
375         }
376
377         private async Task<MediaPacket> ApplyAsync(TransformHandle handle, MediaPacket source,
378             ImageRotation rotation)
379         {
380             SetRotation(handle, rotation);
381             return await RunAsync(handle, source);
382         }
383
384         internal override async Task<MediaPacket> ApplyAsync(MediaPacket source)
385         {
386             using (TransformHandle handle = CreateHandle())
387             {
388                 if (Flip.HasFlag(Flips.Vertical | Flips.Horizontal))
389                 {
390                     var flipped = await ApplyAsync(handle, source, ImageRotation.FlipHorizontal);
391                     try
392                     {
393                         return await ApplyAsync(handle, flipped, ImageRotation.FlipVertical);
394                     }
395                     finally
396                     {
397                         flipped.Dispose();
398                     }
399                 }
400
401                 return await ApplyAsync(handle, source, Flip.HasFlag(Flips.Horizontal) ?
402                     ImageRotation.FlipHorizontal : ImageRotation.FlipVertical);
403             }
404         }
405     }
406
407     /// <summary>
408     /// Changes the colorspace of an image.
409     /// </summary>
410     /// <seealso cref="ColorSpace"/>
411     public class ColorSpaceTransform : ImageTransform
412     {
413         private ImageColorSpace _imageColorSpace;
414
415         /// <summary>
416         /// Initializes a new instance of the <see cref="ColorSpaceTransform"/> class.
417         /// </summary>
418         /// <param name="colorSpace">The colorspace of output image.</param>
419         /// <exception cref="ArgumentException"><paramref name="colorSpace"/> is invalid.</exception>
420         /// <exception cref="NotSupportedException"><paramref name="colorSpace"/> is not supported.</exception>
421         /// <seealso cref="SupportedColorSpaces"/>
422         public ColorSpaceTransform(ColorSpace colorSpace)
423         {
424             ColorSpace = colorSpace;
425         }
426
427         /// <summary>
428         /// Gets or sets the colorspace of the result image.
429         /// </summary>
430         /// <exception cref="ArgumentException"><paramref name="value"/> is invalid.</exception>
431         /// <exception cref="NotSupportedException"><paramref name="value"/> is not supported.</exception>
432         /// <seealso cref="SupportedColorSpaces"/>
433         public ColorSpace ColorSpace
434         {
435             get { return _imageColorSpace.ToCommonColorSpace(); }
436             set
437             {
438                 ValidationUtil.ValidateEnum(typeof(ColorSpace), value, nameof(ColorSpace));
439
440                 _imageColorSpace = value.ToImageColorSpace();
441             }
442         }
443
444         internal override void Configure(TransformHandle handle)
445         {
446             SetColorspace(handle, _imageColorSpace);
447         }
448
449         /// <summary>
450         /// Gets the supported colorspaces for <see cref="ColorSpaceTransform"/>.
451         /// </summary>
452         public static IEnumerable<ColorSpace> SupportedColorSpaces
453         {
454             get
455             {
456                 foreach (ImageColorSpace value in Enum.GetValues(typeof(ImageColorSpace)))
457                 {
458                     yield return value.ToCommonColorSpace();
459                 }
460             }
461         }
462     }
463
464     /// <summary>
465     /// Crops an image.
466     /// </summary>
467     public class CropTransform : ImageTransform
468     {
469         private Rectangle _region;
470
471         /// <summary>
472         /// Initializes a new instance of the <see cref="CropTransform"/> class.
473         /// </summary>
474         /// <param name="region">The crop region.</param>
475         /// <exception cref="ArgumentOutOfRangeException">
476         ///     The X-position of <paramref name="region"/> is less than zero.\n
477         ///     - or -\n
478         ///     The Y-position of <paramref name="region"/> is less than zero.\n
479         ///     - or -\n
480         ///     The width of <paramref name="region"/> is less than or equal to zero.\n
481         ///     - or -\n
482         ///     The height of <paramref name="region"/> is less than or equal to zero.
483         /// </exception>
484         public CropTransform(Rectangle region)
485         {
486             Region = region;
487         }
488
489         /// <summary>
490         /// Gets or sets the crop region.
491         /// </summary>
492         /// <exception cref="ArgumentOutOfRangeException">
493         ///     The X-position of <paramref name="value"/> is less than zero.\n
494         ///     - or -\n
495         ///     The Y-position of <paramref name="value"/> is less than zero.\n
496         ///     - or -\n
497         ///     The width of <paramref name="value"/> is less than or equal to zero.\n
498         ///     - or -\n
499         ///     The height of <paramref name="value"/> is less than or equal to zero.
500         /// </exception>
501         public Rectangle Region
502         {
503             get { return _region; }
504             set
505             {
506
507                 if (value.X < 0)
508                 {
509                     throw new ArgumentOutOfRangeException(nameof(Region), value,
510                         "X position of the region can't be less than zero.");
511                 }
512
513                 if (value.Y < 0)
514                 {
515                     throw new ArgumentOutOfRangeException(nameof(Region), value,
516                         "Y position of the region can't be less than zero.");
517                 }
518
519                 if (value.Width <= 0)
520                 {
521                     throw new ArgumentOutOfRangeException(nameof(Region), value,
522                         "Width of the region can't be less than or equal zero.");
523                 }
524
525                 if (value.Height < 0)
526                 {
527                     throw new ArgumentOutOfRangeException(nameof(Region), value,
528                         "Height of the region can't be less than or equal to zero.");
529                 }
530
531                 _region = value;
532             }
533         }
534
535         internal override void Configure(TransformHandle handle)
536         {
537             SetCropArea(handle, Region.Left, Region.Top, Region.Right, Region.Bottom);
538         }
539     }
540
541     /// <summary>
542     /// Resizes an image.
543     /// </summary>
544     public class ResizeTransform : ImageTransform
545     {
546         private Size _size;
547
548         /// <summary>
549         /// Initializes a new instance of the <see cref="ResizeTransform"/> class.
550         /// </summary>
551         /// <param name="size">The size that an image is resized to.</param>
552         /// <exception cref="ArgumentOutOfRangeException">
553         ///     The width of <paramref name="size"/> is less than or equal to zero.\n
554         ///     - or -\n
555         ///     The height of <paramref name="size"/> is less than or equal to zero.
556         /// </exception>
557         public ResizeTransform(Size size)
558         {
559             Size = size;
560         }
561
562         /// <summary>
563         /// Gets or sets the size that an image is resized to.
564         /// </summary>
565         /// <exception cref="ArgumentOutOfRangeException">
566         ///     The width of <paramref name="value"/> is less than or equal to zero.\n
567         ///     - or -\n
568         ///     The height of <paramref name="value"/> is less than or equal to zero.
569         /// </exception>
570         public Size Size
571         {
572             get { return _size; }
573             set
574             {
575                 if (value.Width <= 0)
576                 {
577                     throw new ArgumentOutOfRangeException(nameof(Size), value,
578                         "Width of the size can't be less than or equal to zero.");
579                 }
580
581                 if (value.Height <= 0)
582                 {
583                     throw new ArgumentOutOfRangeException(nameof(Size), value,
584                         "Height of the size can't be less than or equal to zero.");
585                 }
586
587                 _size = value;
588             }
589         }
590
591         internal override void Configure(TransformHandle handle)
592         {
593             SetResolution(handle, (uint)Size.Width, (uint)Size.Height);
594         }
595     }
596 }