Пример #1
0
        /// <inheritdoc />
        public override PPImage ChangeColorType(PPImage input, SKColorType type, CancellationToken token = default)
        {
            if (input == null)
            {
                throw new ArgumentNullException($"The {nameof(input)} cannot be null");
            }

            NewColorType = type;
            return(new PPImage(input.Bitmap.Copy(NewColorType), input.ImagePath)
            {
                ImageName = input.ImageName
            });
        }
Пример #2
0
 /// <summary>
 /// Converts a value to the <see cref="PPImage"/>
 /// </summary>
 /// <param name="value">The value to be converted</param>
 /// <exception cref="ArgumentException">Is thrown when <paramref name="value"/> is not <see cref="Image"/> or it's source is not <see cref="BitmapSource"/></exception>
 /// <returns>The resulting <see cref="PPImage"/></returns>
 public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
 {
     if (value is Image img)
     {
         if (img.Source is BitmapSource bmp)
         {
             var tmp           = bmp.ToSKBitmap();
             var resultPPImage = new PPImage(tmp);
             tmp.Dispose();
             return(resultPPImage);
         }
     }
     throw new ArgumentException($"{nameof(value)} is not Image or it's source is not BitmapSource");
 }
Пример #3
0
        /// <summary>
        /// Performs the "Cropping" Trim operation (i.e. the Crop)
        /// </summary>
        /// <param name="input">Removes transparent pixels from border of the image permamently</param>
        /// <param name="token">The cancellation token</param>
        /// <remarks>The cropped pixels are reflected in the metadata</remarks>
        /// <returns>Cropped texture, unmodified <paramref name="input"/> if the cancellation was requested</returns>
        public override PPImage Trim(PPImage input, CancellationToken token = default)
        {
            if (input == null)
            {
                throw new ArgumentNullException(nameof(input));
            }

            var result = trimmer.Trim(input, token);

            result.OriginalWidth  = result.Bitmap.Width;
            result.OriginalHeight = result.Bitmap.Height;
            result.OffsetX        = result.OffsetY = 0; //does not keep position
            return(result);
        }
Пример #4
0
        /// <summary>
        /// Saves an image at a given path using a format given by <paramref name="format"/>
        /// </summary>
        /// <param name="image">The image to be saved</param>
        /// <param name="path">The path where the image should be saved</param>
        /// <param name="format">Format of the image</param>
        /// <param name="overwrite">Determines whether the file should be overriden if it already exists</param>
        /// <returns></returns>
        public static bool SaveBitmap(PPImage image, string path, SKEncodedImageFormat format, bool overwrite = true)
        {
            if (!overwrite && File.Exists(path))
            {
                return(false);
            }

            using (var img = SKImage.FromBitmap(image.Bitmap))
                using (var sw = File.OpenWrite(path))
                    using (var data = img.Encode(format, 100))
                    {
                        data.SaveTo(sw);
                    }

            return(true);
        }
        /// <summary>
        /// Returns a neighborhood pixels of the pixel at a given index (index in the flat 1D instead of 2D array) within the given image
        /// </summary>
        /// <remarks>The neighborhood is "Moore neighborhood"</remarks>
        /// <param name="index">The index of the pixel for which the neighborhood should be returned</param>
        /// <param name="image">The image containing the pixel at the given index</param>
        /// <returns>The indices of the neighborhood pixels</returns>
        private static IEnumerable <int> GetNeighborhood(int index, PPImage image)
        {
            yield return(index - 1);

            yield return(index + 1);

            yield return(index - image.Bitmap.Width);

            yield return(index + image.Bitmap.Width);

            yield return(index - image.Bitmap.Width - 1);

            yield return(index - image.Bitmap.Width + 1);

            yield return(index + image.Bitmap.Width - 1);

            yield return(index + image.Bitmap.Width + 1);
        }
        /// <inheritdoc />
        public override PPImage RemoveBackground(PPImage input, CancellationToken token = default)
        {
            if (input == null)
            {
                throw new ArgumentNullException($"The {nameof(input)} cannot be null");
            }

            var component = SelectBackground(input);

            //Cancellation was requested during the SelectBackground method
            if (component == null)
            {
                return(input);
            }

#pragma warning disable CA2000 // The resulting PPImage is the owner of the bitmap
            SKBitmap bmp2 = new SKBitmap(input.Bitmap.Width, input.Bitmap.Height, input.Bitmap.ColorType, SKAlphaType.Premul)
            {
                Pixels = input.Bitmap.Pixels
            };
#pragma warning restore CA2000 // So it should not get disposed there
            bmp2.Erase(SKColors.Transparent);

            foreach (var pix in component)
            {
                if (token.IsCancellationRequested)
                {
                    return(input);
                }
                bmp2.SetPixel(pix % input.Bitmap.Width, pix / input.Bitmap.Width, SKColors.Transparent);
            }

            return(new PPImage(bmp2, input.ImagePath)
            {
                ImageName = input.ImageName
            });
        }
Пример #7
0
        /// <summary>
        /// Returns an image of the texture atlas
        /// </summary>
        /// <returns>The image of the texture atlas</returns>
        private PPImage GetImage()
        {
            PPImage image = new PPImage(Bitmap);

            return(image);
        }
Пример #8
0
        /// <inheritdoc />
        public override PPImage Trim(PPImage input, CancellationToken token = default)
        {
            if (input == null)
            {
                throw new ArgumentNullException($"The {nameof(input)} cannot be null");
            }

            int origWidth  = input.Bitmap.Width;
            int origHeight = input.Bitmap.Height;

            int  left = 0, right = input.Bitmap.Width, top = input.Bitmap.Height, bottom = 0;
            var  pixels = input.Bitmap.Pixels;
            bool topTransparent = true, bottomTransparent = true, leftTransparent = true, rightTransparent = true;

            while (true)
            {
                if (token.IsCancellationRequested)
                {
                    return(input);
                }
                //process top and bottom lines (horizontal)
                for (int i = left; i < right; i++)
                {
                    if (pixels[bottom * input.Bitmap.Width + i].Alpha > alphaTolerance)
                    {
                        bottomTransparent = false;
                    }
                    if (pixels[(top - 1) * input.Bitmap.Width + i].Alpha > alphaTolerance)
                    {
                        topTransparent = false;
                    }
                }

                if (bottomTransparent)
                {
                    bottom++;
                }
                if (topTransparent)
                {
                    top--;
                }

                //process vertical lines
                for (int i = bottom; i < top; i++)
                {
                    if (pixels[i * input.Bitmap.Width + left].Alpha > alphaTolerance)
                    {
                        leftTransparent = false;
                    }
                    if (pixels[i * input.Bitmap.Width + (right - 1)].Alpha > alphaTolerance)
                    {
                        rightTransparent = false;
                    }
                }
                if (leftTransparent)
                {
                    left++;
                }
                if (rightTransparent)
                {
                    right--;
                }

                if (!(bottomTransparent || topTransparent || leftTransparent || rightTransparent))
                {
                    break;
                }
                if (left >= (input.Bitmap.Width - 1) && right <= 0 && top <= 0 && bottom >= (input.Bitmap.Height - 1))
                {
                    return(new PPImage());
                }
            }
            SKBitmap result = new SKBitmap();

            input.Bitmap.ExtractSubset(result, new SKRectI(left, bottom, right, top));
            var resultImage = new PPImage(result, input.ImagePath)
            {
                ImageName      = input.ImageName,
                OriginalWidth  = origWidth,
                OriginalHeight = origHeight,
                OffsetX        = left,
                OffsetY        = bottom //actually top in "graphic field jargon" (i.e. the lower coordinates)
            };

            return(resultImage);
        }
Пример #9
0
 /// <inheritdoc />
 public PPImage ProcessImage(PPImage input, CancellationToken token = default)
 {
     return(ChangeColorType(input, NewColorType, token));
 }
Пример #10
0
 /// <summary>
 /// Performs the ColorType change operation
 /// </summary>
 /// <param name="input">Input image</param>
 /// <param name="targetColorType">The target color type</param>
 /// <param name="token">The cancellation token</param>
 /// <remarks>The input image is copied, processed and the processed version is then returned</remarks>
 /// <exception cref="ArgumentNullException">Is thrown when the <paramref name="input"/> is null</exception>
 /// <returns>An image with a color type changed to the target color type, unmodified <paramref name="input"/> when the cancellation was requested</returns>
 public abstract PPImage ChangeColorType(PPImage input, SKColorType targetColorType, CancellationToken token = default);
Пример #11
0
 /// <inheritdoc />
 public override PPImage ChangeColorType(PPImage input, CancellationToken token = default)
 {
     return(ChangeColorType(input, NewColorType, token));
 }
Пример #12
0
 /// <summary>
 /// Performs the remove background operation
 /// </summary>
 /// <param name="input">Input image</param>
 /// <param name="token">The cancellation token</param>
 /// <remarks>The input image is copied, processed and the processed version is then returned</remarks>
 /// <exception cref="ArgumentNullException">Is thrown when the <paramref name="input"/> is null</exception>
 /// <returns>An image with background removed, unmodified <paramref name="input"/> when cancellation was requested</returns>
 public abstract PPImage RemoveBackground(PPImage input, CancellationToken token = default);
Пример #13
0
 /// <inheritdoc />
 public PPImage ProcessImage(PPImage input, CancellationToken token = default)
 {
     return(RemoveBackground(input, token));
 }
Пример #14
0
 /// <summary>
 /// Performs the Extrude operation
 /// </summary>
 /// <param name="input">Input image</param>
 /// <param name="token">The cancellation token</param>
 /// <remarks>The input image is copied, processed and the processed version is then returned</remarks>
 /// <returns>An image extruded by a <see cref="Amount"/> of pixels</returns>
 public PPImage Extrude(PPImage input, CancellationToken token = default)
 {
     return(Extrude(input, Amount, token));
 }
Пример #15
0
 /// <inheritdoc />
 public PPImage ProcessImage(PPImage input, CancellationToken token = default)
 {
     return(Trim(input, token));
 }
Пример #16
0
 /// <summary>
 /// Adds a padding, i.e. <see cref="Amount"/> of transparent pixels to each side of the <paramref name="input"/> image's border
 /// </summary>
 /// <param name="input">The input image</param>
 /// <param name="token">The cancellation token</param>
 /// <returns>The copy of the input texture with added padding or unmodified input texture if the cancellation was requested</returns>
 public PPImage AddPadding(PPImage input, CancellationToken token = default)
 {
     return(AddPadding(input, Amount, token));
 }
Пример #17
0
        /// <summary>
        /// Adds a padding, i.e. <paramref name="amount"/> of transparent pixels to each side of the <paramref name="input"/> image's border
        /// </summary>
        /// <param name="input">The input image</param>
        /// <param name="amount">Amount of transparent pixels to add</param>
        /// <param name="token">The cancellation token</param>
        /// <remarks>Changes the <see cref="Amount"/> to <paramref name="amount"/></remarks>
        /// <returns>The copy of the input texture with added padding or unmodified input texture if the cancellation was requested</returns>
        public PPImage AddPadding(PPImage input, int amount, CancellationToken token = default)
        {
            if (amount < 0)
            {
                throw new ArgumentOutOfRangeException($"The {nameof(amount)} has to be non-negative integer");
            }

            if (input == null)
            {
                throw new ArgumentNullException($"The {nameof(input)} cannot be null");
            }

            //Do not allow to go beyond this size (because the use of Int32.MaxValue >> 1 in the packing algorithms
            //And because width > height must fit into Int32.MaxValue
            var area = (long)(input.Bitmap.Width + amount) * (input.Bitmap.Height + amount);

            if (area > int.MaxValue)
            {
                return(input);
            }

            Amount = amount;
#pragma warning disable CA2000 // The resulting PPImage is the owner of the bitmap
            SKBitmap result = new SKBitmap(input.Bitmap.Width + 2 * amount, input.Bitmap.Height + 2 * amount);
#pragma warning restore CA2000 // So it should not get disposed there
            result.Erase(SKColors.Transparent);
            var srcPixels = input.Bitmap.Pixels;

            for (int i = 0; i < input.Bitmap.Height; i++)
            {
                for (int j = 0; j < input.Bitmap.Width; j++)
                {
                    result.SetPixel(j + amount, i + amount, srcPixels[i * input.Bitmap.Width + j]); //copy
                }
                //Do not ask too often, that is the reason why the check is after the inner loop
                if (token.IsCancellationRequested)
                {
                    return(input);
                }
            }

            //for (int i = 0; i < Amount; i++)
            //{
            //    if (token.IsCancellationRequested)
            //    {
            //        return input;
            //    }

            //    for (int x = 0; x < input.Bitmap.Width; x++)
            //    {
            //        //The strip above
            //        result.SetPixel(x, i, SKColors.Transparent);
            //        //The strip below
            //        result.SetPixel(x, input.Bitmap.Height - 1 + i, SKColors.Transparent);
            //    }

            //    if (token.IsCancellationRequested)
            //    {
            //        return input;
            //    }

            //    for (int y = 0; y < input.Bitmap.Height; y++)
            //    {
            //        //The strip to the left
            //        result.SetPixel(i, y, SKColors.Transparent);
            //        //The strip to the right
            //        result.SetPixel(input.Bitmap.Width - 1 + i, y, SKColors.Transparent);
            //    }
            //}

            var resImage = new PPImage(result, input.ImagePath)
            {
                ImageName = input.ImageName,
            };

            resImage.NoWhiteSpaceXOffset += Amount;
            resImage.NoWhiteSpaceYOffset += Amount;
            resImage.FinalWidth          -= 2 * Amount;
            resImage.FinalHeight         -= 2 * Amount;

            return(resImage);
        }
Пример #18
0
 /// <summary>
 /// Performs the Trim operation
 /// </summary>
 /// <param name="input">Input image</param>
 /// <param name="token">The cancellation token</param>
 /// <remarks>The input image is copied, processed and the processed version is then returned</remarks>
 /// <exception cref="ArgumentNullException">Is thrown when the <paramref name="input"/> is null</exception>
 /// <returns>Trimmed image, null if the cancellation was requested</returns>
 public abstract PPImage Trim(PPImage input, CancellationToken token = default);
        /// <summary>
        /// Selects a guessed background of the input image
        /// </summary>
        /// <param name="input">The input image</param>
        /// <param name="token">The cancellation token</param>
        /// <remarks>Performs a BFS</remarks>
        /// <returns>Returns the list of pixels having the same color as the color of the guessed background, null when cancellation was requested</returns>
        private List <int> SelectBackground(PPImage input, CancellationToken token = default)
        {
            SKColor bgColor = SKColors.Transparent;

            Dictionary <SKColor, List <int> > components = new Dictionary <SKColor, List <int> >();

            int[] cornerIndices = new int[] { 0,                                              //top-left
                                              input.Bitmap.Width - 1,                         //top-right
                                              (input.Bitmap.Height - 1) * input.Bitmap.Width, //bottom-left
                                              input.Bitmap.Width *input.Bitmap.Height - 1 };  //bottom-right};

            var pixels = input.Bitmap.Pixels;

            SKColor[] cornerColors = new SKColor[4]
            {
                pixels[cornerIndices[0]],
                pixels[cornerIndices[1]],
                pixels[cornerIndices[2]],
                pixels[cornerIndices[3]]
            };

            bool[] visited = new bool[input.Bitmap.Width * input.Bitmap.Height];
            Dictionary <SKColor, float> colorCounts = new Dictionary <SKColor, float>();

            Queue <int> toVisit = new Queue <int>();

            foreach (var corner in cornerIndices)
            {
                if (token.IsCancellationRequested)
                {
                    return(null);
                }
                toVisit.Enqueue(corner);
                visited[corner] = true;
            }

            if (cornerColors.All(x => x == cornerColors[0]))
            {
                colorCounts.Add(cornerColors[0], 0.0f);
                components.Add(cornerColors[0], new List <int>());
                int totalVisited = 0;
                while (toVisit.Count > 0)
                {
                    if (token.IsCancellationRequested)
                    {
                        return(null);
                    }
                    var curr = toVisit.Dequeue();
                    totalVisited++;
                    foreach (var neigh in GetNeighborhood(curr, input).Where(x => x != curr && x >= 0 && x < input.Bitmap.Width * input.Bitmap.Height))
                    {
                        if (visited[neigh] || pixels[neigh] != cornerColors[0])
                        {
                            continue;
                        }
                        toVisit.Enqueue(neigh);
                        visited[neigh] = true;
                    }
                    if (pixels[curr] == cornerColors[0])
                    {
                        components[cornerColors[0]].Add(curr);
                        colorCounts[cornerColors[0]] += 1.0f;
                    }
                }

                return(components[cornerColors[0]]);
            }
            else
            {
                var uniqueColors = cornerColors.Distinct();
                foreach (var x in uniqueColors)
                {
                    components.Add(x, new List <int>());
                    colorCounts.Add(x, 0.0f);
                }

                while (toVisit.Count > 0)
                {
                    if (token.IsCancellationRequested)
                    {
                        return(null);
                    }
                    var curr = toVisit.Dequeue();
                    foreach (var neigh in GetNeighborhood(curr, input).Where(z => z != curr && z >= 0 && z < input.Bitmap.Width * input.Bitmap.Height))
                    {
                        if (visited[neigh] || !cornerColors.Contains(pixels[neigh]))
                        {
                            continue;
                        }
                        toVisit.Enqueue(neigh);
                        visited[neigh] = true;
                    }
                    //if (uniqueColors.Contains(input.Pixels[curr]))
                    //{
                    components[pixels[curr]].Add(curr);
                    float x       = curr % input.Bitmap.Width;
                    float y       = curr / input.Bitmap.Width;
                    float middleX = input.Bitmap.Width / 2.0f;
                    float middleY = input.Bitmap.Height / 2.0f;
                    colorCounts[pixels[curr]] += Max(Abs(middleX - x), Abs(middleY - y));
                    //colorCounts[input.Pixels[curr]] += 1.0f;
                    //}
                    visited[curr] = true;
                }

                bgColor = colorCounts.OrderByDescending(x => x.Value).First().Key;
                return(components[bgColor]);
            }

            //return bgColor;
        }
Пример #20
0
        /// <summary>
        /// Performs the Trim operation with a specified amount
        /// </summary>
        /// <param name="input">Input image</param>
        /// <param name="amount">Amount of pixels to be added</param>
        /// <param name="token">The cancellation token</param>
        /// <remarks>
        /// The input image is copied, processed and the processed version is then returned
        /// Changes the <see cref="Amount"/> to <paramref name="amount"/>
        /// </remarks>
        /// <exception cref="ArgumentNullException">Is thrown when the <paramref name="input"/> is null</exception>
        /// <exception cref="ArgumentOutOfRangeException">Is thrown when the <paramref name="amount"/> is negative</exception>
        /// <returns>An image extruded by a <paramref name="amount"/> of pixels, unmodified <paramref name="input"/> if cancellation was requested</returns>
        public PPImage Extrude(PPImage input, int amount, CancellationToken token = default)
        {
            if (amount < 0)
            {
                throw new ArgumentOutOfRangeException($"The {nameof(amount)} has to be non-negative integer");
            }

            if (input == null)
            {
                throw new ArgumentNullException($"The {nameof(input)} cannot be null");
            }

            //Do not allow to go beyond this size (because the use of Int32.MaxValue >> 1 in the packing algorithms
            //And because width > height must fit into Int32.MaxValue
            var area = (long)(input.Bitmap.Width + amount) * (input.Bitmap.Height + amount);

            if (area > int.MaxValue)
            {
                return(input);
            }

            Amount = amount;
#pragma warning disable CA2000 // The resulting PPImage is the owner of the bitmap
            SKBitmap result = new SKBitmap(input.Bitmap.Width + 2 * amount, input.Bitmap.Height + 2 * amount);
#pragma warning restore CA2000 // So it should not get disposed there
            result.Erase(SKColors.Transparent);
            var srcPixels = input.Bitmap.Pixels;

            for (int i = 0; i < input.Bitmap.Height; i++)
            {
                for (int j = 0; j < input.Bitmap.Width; j++)
                {
                    result.SetPixel(j + amount, i + amount, srcPixels[i * input.Bitmap.Width + j]); //copy
                }
                //Do not ask too often, that is the reason why the check is after the inner loop
                if (token.IsCancellationRequested)
                {
                    return(input);
                }
            }

            //top & bottow rows
            for (int i = 0; i < input.Bitmap.Width; i++)
            {
                for (int j = 0; j < amount; j++)
                {
                    result.SetPixel(i + amount, j, srcPixels[i]);
                    result.SetPixel(i + amount, input.Bitmap.Height + j + amount, srcPixels[srcPixels.Length - 1 - ((input.Bitmap.Width - 1) - i)]);
                }
                //Do not ask too often, that is the reason why the check is after the inner loop
                if (token.IsCancellationRequested)
                {
                    return(input);
                }
            }

            //left & right columns
            for (int i = 0; i < input.Bitmap.Height; i++)
            {
                for (int j = 0; j < amount; j++)
                {
                    result.SetPixel(j, i + amount, srcPixels[i * input.Bitmap.Width]);
                    result.SetPixel(j + input.Bitmap.Width + amount, i + amount, srcPixels[i * input.Bitmap.Width + input.Bitmap.Width - 1]);
                }
                //Do not ask too often, that is the reason why the check is after the inner loop
                if (token.IsCancellationRequested)
                {
                    return(input);
                }
            }

            //diags
            for (int i = 0; i < amount; i++)
            {
                for (int j = 0; j < amount; j++)
                {
                    result.SetPixel(i, j, srcPixels[0]);                                                                                   //left top
                    result.SetPixel(i, result.Height - j - 1, srcPixels[input.Bitmap.Width * (input.Bitmap.Height - 1)]);                  //left bottom
                    result.SetPixel(result.Width - i - 1, j, srcPixels[input.Bitmap.Width - 1]);                                           //top right
                    result.SetPixel(result.Width - i - 1, result.Height - j - 1, srcPixels[input.Bitmap.Width * input.Bitmap.Height - 1]); //right bottom
                }
                //Do not ask too often, that is the reason why the check is after the inner loop
                if (token.IsCancellationRequested)
                {
                    return(input);
                }
            }

            var resImage = new PPImage(result, input.ImagePath)
            {
                ImageName = input.ImageName
            };

            resImage.NoWhiteSpaceXOffset += Amount;
            resImage.NoWhiteSpaceYOffset += Amount;
            resImage.FinalWidth          -= 2 * Amount;
            resImage.FinalHeight         -= 2 * Amount;

            return(resImage);
        }
Пример #21
0
 /// <summary>
 /// Constructs the ViewModel from a <paramref name="image"/>
 /// </summary>
 /// <param name="image">Image for which the ViewModel is constructed</param>
 public ImageViewModel(PPImage image)
 {
     Image = image;
 }