I am building a function in C# that will slice a frame and then count the white pixels in that slice. I want to be able to uses this function on multiple frames/pictureBoxs, so I need to change the name of label.Text to the correct label. How should I go about this? I'll be using mergedImage, mergedImage2, and BinaryImage. Each Image will also have outerRight, innerRight, Cent, innerLeft, outerLeft.
private void Slicing(Mat inputFrame)
{
Mat frame = inputFrame.Clone();
//count the number of white pixels
int whitePixelsExtendedOutterLeft = 0;
int whitePixelsExtendedInnerLeft = 0;
int whitePixelsExtendedCent = 0;
int whitePixelsExtendedInnerRight = 0;
int whitePixelsExtendedOutterRight = 0;
Image<Gray, byte> img = frame.ToImage<Gray, byte>();
//Calculates the outter left slice
for (int i = 0; i <= 4; i++)
{
for (int x = i; (frame.Width / 5) * i < (frame.Width / 5) * (i+1) ; x++)
{
for (int y = 0; y < frame.Height; y++)
{
switch (i)
{
case 0:
if (img.Data[y, x, 0] == 255) whitePixelsExtendedOutterLeft++;
break;
case 1:
if (img.Data[y, x, 0] == 255) whitePixelsExtendedInnerLeft++;
break;
case 2:
if (img.Data[y, x, 0] == 255) whitePixelsExtendedCent++;
break;
case 3:
if (img.Data[y, x, 0] == 255) whitePixelsExtendedInnerRight++;
break;
case 4:
if (img.Data[y, x, 0] == 255) whitePixelsExtendedOutterRight++;
break;
}
}
}
}
//displays their respective white pixel count to form1
Invoke(new Action(() =>
{
mergedImage.Text = $"{whitePixelsExtendedOutterLeft} Outter Left ";
//whitePixelsExtendedOutputLIL.Text = $"{whitePixelsExtendedInnerLeft} Inner Left ";
//whitePixelsExtendedOutputLCent.Text = $"{whitePixelsExtendedCent} Center ";
//whitePixelsExtendedOutputLIR.Text = $"{whitePixelsExtendedInnerRight} Inner Right ";
//whitePixelsExtendedOutputLOR.Text = $"{whitePixelsExtendedOutterRight} Outter Right ";
}));
}
I have tried concatenation of the of the label like: "mergedImage" ++ i .Text . of course this did not work.
I'm writing a small steganography application in C# and was able to hide text in images. However the method I used was the GetPixel/SetPixel method which was a lot slower for larger images, which I noticed after trying to hide a mp3 file in the image. After some google searches, I found out about LockBits. While the speed did improve drastically, I discovered that I was unable to extract the encrypted data (the cipher text) which was hidden in the image.
I'm not sure if the issue is with how I insert the data or when extracting it. When attempting to extract the Base64 cipher text, it would be corrupted (random symbols and characters) and throw an exception about it not being a Base64String. I ended up changing the code by following what was on the documentation for LockBits, I'll paste it below.
Merging the cipher text
public static unsafe void MergeEncryptedData(string data, Bitmap bmp, string output) {
State s = State.HIDING;
int height = bmp.Height;
int width = bmp.Width;
var bitmapData = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, bmp.PixelFormat);
byte * scan0 = (byte * ) bitmapData.Scan0;
int bytesPerPixel = 4;
int dataIndex = 0;
byte dataValue = 0;
long colorUnitIndex = 0;
int zeros = 0;
byte R, G, B;
Parallel.For(0, height, (i, loopState) = > {
byte * currentLine = scan0 + (i * bitmapData.Stride);
for (int j = 0; j < (bitmapData.Width * bytesPerPixel); j += bytesPerPixel) {
R = currentLine[i + 2];
G = currentLine[i + 1];
B = currentLine[i];
for (int n = 0; n < 3; n++) {
if (colorUnitIndex % 8 == 0) {
if (zeros == 8) {
if ((colorUnitIndex - 1) % 3 < 2) {
currentLine[i + 2] = R;
currentLine[i + 1] = G;
currentLine[i] = B;
//bmp.SetPixel(j, i, Color.FromArgb(R, G, B));
}
loopState.Stop();
}
if (dataIndex >= data.Length) {
s = State.FILL_WITH_ZEROS;
} else {
dataValue = (byte) data[dataIndex++];
}
}
switch (colorUnitIndex % 3) {
case 0:
{
if (s == State.HIDING) {
B += (byte)(dataValue % 2);
dataValue /= 2;
}
}
break;
case 1:
{
if (s == State.HIDING) {
G += (byte)(dataValue % 2);
dataValue /= 2;
}
}
break;
case 2:
{
if (s == State.HIDING) {
R += (byte)(dataValue % 2);
dataValue /= 2;
}
currentLine[i + 2] = R;
currentLine[i + 1] = G;
currentLine[i] = B;
//bmp.SetPixel(j, i, Color.FromArgb(R, G, B));
}
break;
}
colorUnitIndex++;
if (s == State.FILL_WITH_ZEROS) {
zeros++;
}
}
}
});
bmp.UnlockBits(bitmapData);
bmp.Save(output, ImageFormat.Png);
}
Extracting the cipher text
public static unsafe string ExtractData(Bitmap bmp) {
int height = bmp.Height;
int width = bmp.Width;
var bitmapData = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, bmp.PixelFormat);
byte * scan0 = (byte * ) bitmapData.Scan0.ToPointer();
int bytesPerPixel = 4;
int colorUnitIndex = 0;
int charValue = 0;
string extractedText = String.Empty;
Parallel.For(0, height, (i, loopState) = > {
byte * currentLine = scan0 + (i * bitmapData.Stride);
for (int j = 0; j < (bitmapData.Width * bytesPerPixel); j += bytesPerPixel) {
for (int n = 0; n < 3; n++) { //this particular loop feels incorrect
switch (colorUnitIndex % 3) {
case 0:
{
charValue = charValue * 2 + currentLine[i] % 2;
}
break;
case 1:
{
charValue = charValue * 2 + currentLine[i + 1] % 2;
}
break;
case 2:
{
charValue = charValue * 2 + currentLine[i + 2] % 2;
}
break;
}
colorUnitIndex++;
if (colorUnitIndex % 8 == 0) {
charValue = reverseBits(charValue);
if (charValue == 0) {
loopState.Stop();
}
char c = (char) charValue;
extractedText += c.ToString();
}
}
}
});
bmp.UnlockBits(bitmapData);
return extractedText;
}
An example of what the extracted cipher text looks like when the error is thrown:
I$I$I$I$I$I$I$I$I$I$I$I$I$I䥉II!J$$.
It should be a Base-64 String
Just for reference, I'm using a LUT PNG image to hide the data, so I'm able see a slight difference in color when compared to the original. So I know the RGB values are indeed being changed.
You need to consider:
Stride - image data with may be different of image width. More info here.
Image number of color/data channels
8 bpp (1 channel)
24 bpp (3 channels RGB)
32 bpp (4 channels ARGB, where A means alpha, transparency)
You mention RGB PNG but you are using 4 channels in your code (ARGB), double check that.
Here is a sample method that WILL obtain the same data that using the slow Bitmap GetPixel, but really fast. Based on this sample you can fix your code, as:
How to calculate bits per pixel
How to use stride properly
How to read multiple channels
Code:
/// <summary>
/// Get pixel directly from unmanaged pixel data based on the Scan0 pointer.
/// </summary>
/// <param name="bmpData">BitmapData of the Bitmap to get the pixel</param>
/// <param name="p">Pixel position</param>
/// <returns>Pixel value</returns>
public static byte[] GetPixel(BitmapData bmpData, Point p)
{
if ((p.X > bmpData.Width - 1) || (p.Y > bmpData.Height - 1))
throw new ArgumentException("GetPixel Point p is outside image bounds!");
int bitsPerPixel = ((int)bmpData.PixelFormat >> 8) & 0xFF;
int channels = bitsPerPixel / 8;
byte[] data = new byte[channels];
int id = p.Y * bmpData.Stride + p.X * channels;
unsafe
{
byte* pData = (byte*)bmpData.Scan0;
for (int i = 0; i < data.Length; i++)
{
data[i] = pData[id + i];
}
}
return data;
}
I'm trying to create a simple edge detection filter. And as I said it works with only one image. I'm trying to create this filter with 2 steps.
Blurring image (with meanfilter)
Calculate ( Original image-Blurring image)
The first step works well. And code of second one is simple like first one. But I see an error message:
System.ArgumentOutOfRangeException: 'Parameter must be positive and < Height.
Parameter name: y'
Working image:https://i.hizliresim.com/dLXkbn.png
My code:
public void edgedetectionfilter( )
{
Bitmap InputPicture,BlurredPicture, OutputPicture;
InputPicture = new Bitmap(pBox_SOURCE.Image);
BlurredPicture = new Bitmap(pBox_PROCESSED.Image);
int PicWidth = InputPicture.Width;
int PicHeight= InputPicture.Height;
OutputPicture = new Bitmap(PicWidth, PicHeight);
OutputPicture = InputPicture;
int x, y, difR, difG, difB;
Color OrgPicColoValue,BluredPicColorValue;
for (x = 0; x < PicWidth; x++)
{
for (y = 0; y < PicWidth; y++)
{
BluredPicColorValue = BlurredPicture.GetPixel(x, y);
OrgPicColoValue = InputPicture.GetPixel(x, y); //ERROR LINE
difR = Convert.ToInt16(OrgPicColoValue.R -BluredPicColorValue.R);
difG = Convert.ToInt16(OrgPicColoValue.G- BluredPicColorValue.G );
difB = Convert.ToInt16(OrgPicColoValue.B- BluredPicColorValue.B);
if (difR > 255) difR = 255;
if (difG > 255) difG = 255;
if (difB > 255) difB = 255;
if (difR < 0) difR = 0;
if (difG < 0) difG = 0;
if (difB < 0) difB = 0;
OutputPicture.SetPixel(x, y, Color.FromArgb(difR, difG, difB));
}
}
pBoxMedian.Image = OutputPicture;
}
public void meanfilter(int p)
//KERNELSIZE=P
{
if (sliderKernel.Value % 2 == 0)
{
MessageBox.Show("Enter an odd number");
return;
}
Color ColorValue;
Bitmap InputPicture, OutputPicture;
InputPicture = new Bitmap(pBox_SOURCE.Image);
int PicWidth = InputPicture.Width;
int PicHeight= InputPicture.Height;
OutputPicture = new Bitmap(PicWidth, PicHeight);
OutputPicture = InputPicture;
int x, y, i, j, sumR, sumG, sumB, avgR, avgG, avgB;
for (x = (KernelSize - 1) / 2; x < PicWidth - (KernelSize - 1) / 2; x++)
{
for (y = (KernelSize - 1) / 2; y < PicHeight - (KernelSize- 1) / 2; y++)
{
toplamR = 0;
toplamG = 0;
toplamB = 0;
for (i = -((KernelSize - 1) / 2); i <= (KernelSize - 1) / 2; i++)
{
for (j = -((KernelSize - 1) / 2); j <= (KernelSize - 1) / 2; j++)
{
ColorValue= InputPicture.GetPixel(x + i, y + j);
sumR = sumR + ColorValue.R;
sumG = sumG + ColorValue.G;
sumB = sumB + ColorValue.B;
}
}
avgR = sumR / (KernelSize * KernelSize );
avgG = sumG / (KernelSize *KernelSize );
avgB = sumB / (KernelSize * KernelSize );
OutputPicture.SetPixel(x, y, Color.FromArgb(avgR, avgG, avgB));
}
}
pBox_PROCESSED.Image = OutputPicture;
}
You compare y < PicWidth, whereas you probably want y < PicHeight. Is the image it worked on square, by chance?
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
Tihs is a code that impliments the adaptive histogram equalization algorithm,called by a button in the c# main form, the image is about 1024*768 in size. The problem is this code is too slow ,I don't know where I should modify to improve the performance...Please give me some advice....thanks..
private void AHE_BMP_advanced(Int32 halfblocksize)
{
//adaptive histogram equalization
Size imgsz = sourceBMP.Size;
//compute total number of pixels
double totalNum = imgsz.Height * imgsz.Width;
//temp image for storation
Bitmap tempImg = new Bitmap(imgsz.Width, imgsz.Height);
//region statistics
double[,] prob = new double[256, 3];
Int32[,] mapping = new Int32[256, 3];
double[] probSum = new double[3];
for (int i = 0; i < imgsz.Height; i++)
{
for (int j = 0; j < imgsz.Width; j++)
{
//this.textBox2.Text = "i=" + i.ToString() + "j=" + j.ToString();
for (int u = 0; u < 256; u++) {
for (int v = 0; v < 3; v++) {
prob[u, v] = 0;
mapping[u, v] = 0;
}
}
//produce ahe for this pixel:
for (int u = i - halfblocksize; u <= i + halfblocksize; u++)
{
for (int v = j - halfblocksize; v <= j + halfblocksize; v++)
{
//uv->hi,wi;
int hi, wi;
hi = u;
wi = v;
//mirror:
if (hi < 0) hi = -hi;
else if (hi >= imgsz.Height)
hi = 2 * (imgsz.Height - 1) - hi;
if (wi < 0) wi = -wi;
else if (wi >= imgsz.Width)
wi = 2 * (imgsz.Width - 1) - wi;
//get hist
prob[sBmpdata[wi,hi,0], 0] += 1;
prob[sBmpdata[wi,hi,1], 1] += 1;
prob[sBmpdata[wi,hi,2], 2] += 1;
}
}
//get ahe value:
//probSum init:
probSum[0] = 0;
probSum[1] = 0;
probSum[2] = 0;
for (int k = 0; k < 256; k++)
{
this.textBox2.Text += "prob[" + k.ToString()+ ",0]=" +
prob[k,0].ToString()+"\r\n";
prob[k, 0] /= totalNum;
prob[k, 1] /= totalNum;
prob[k, 2] /= totalNum;
//Sum
probSum[0] += prob[k, 0];
probSum[1] += prob[k, 1];
probSum[2] += prob[k, 2];
if(i==40&&j==40)
//mapping(INT32)
mapping[k, 0] = Convert.ToInt32(255.0 * probSum[0]);
mapping[k, 1] = Convert.ToInt32(255.0 * probSum[1]);
mapping[k, 2] = Convert.ToInt32(255.0 * probSum[2]);
}
tempImg.SetPixel(j, i,
Color.FromArgb(mapping[sBmpdata[j,i,0], 0],
mapping[sBmpdata[j,i,1], 1], mapping[sBmpdata[j,i,2], 2]));
}
}
this.pictureBox1.Image = tempImg;
}
SetPixel
SetPixel is very slow. Look in to using LockBits. MSDN has good example.
String concatenation inside a loop
This line inside a loop is also inefficient as it creates 256 strings for each pixel, so 201 million strings allocated, that's got to be expensive!
for (int k = 0; k < 256; k++)
this.textBox2.Text += "prob[" + k.ToString()+ ",0]=" + prob[k,0].ToString()+"\r\n";
If it's debug, take it out, 201 million lines of debug text is not useful to you. It you need it you are better off writing to a file as otherwise it's going to take many GB's of ram to store the final string.
Using SetPixel is actually a fairly inefficient way of working with image data. If you want to scan across the whole image I'd suggest manipulating the image data directly using the BitmapData class.
// Create a new bitmap.
Bitmap bmp = new Bitmap("c:\\fakePhoto.jpg");
// Lock the bitmap's bits.
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
System.Drawing.Imaging.BitmapData bmpData =
bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite,
bmp.PixelFormat);
// Get the address of the first line.
IntPtr ptr = bmpData.Scan0;
// Declare an array to hold the bytes of the bitmap.
int bytes = Math.Abs(bmpData.Stride) * bmp.Height;
byte[] rgbValues = new byte[bytes];
// Copy the RGB values into the array.
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);
// Set every third value to 255. A 24bpp bitmap will look red.
for (int counter = 2; counter < rgbValues.Length; counter += 3)
rgbValues[counter] = 255;
// Copy the RGB values back to the bitmap
System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes);
// Unlock the bits.
bmp.UnlockBits(bmpData);
// Draw the modified image.
e.Graphics.DrawImage(bmp, 0, 150);
Well, I think you should write
private void AHE_BMP_advanced(Int32 halfblocksize)
{
this.pictureBox1.Image = GetAHE_BMP_advanced(halfblocksize, sourceBMP.Size, sBmpdata);
}
where GetAHE_BMP_advanced is
private static Bitmap GetAHE_BMP_advanced(int halfblocksize, Size sourceBmpSize, int[,,] sourceBmpData)
{
const int m = 256;
const int n = 3;
//adaptive histogram equalization
Size imgsz = sourceBmpSize;
//compute total number of pixels
double totalNum = imgsz.Height * imgsz.Width;
var colors = new Color[sourceBmpSize.Width, sourceBmpSize.Height];
for (int i = 0; i < imgsz.Height; i++)
{
for (int j = 0; j < imgsz.Width; j++)
{
double[,] prob = new double[m, n];
int[,] mapping = new int[m, n];
//produce ahe for this pixel:
for (int u = i - halfblocksize; u <= i + halfblocksize; u++)
{
for (int v = j - halfblocksize; v <= j + halfblocksize; v++)
{
int hi = u;
int wi = v;
//mirror:
if (hi < 0) hi = -hi;
else if (hi >= imgsz.Height)
hi = 2 * (imgsz.Height - 1) - hi;
if (wi < 0) wi = -wi;
else if (wi >= imgsz.Width)
wi = 2 * (imgsz.Width - 1) - wi;
//get hist
prob[sourceBmpData[wi, hi, 0], 0] += 1;
prob[sourceBmpData[wi, hi, 1], 1] += 1;
prob[sourceBmpData[wi, hi, 2], 2] += 1;
}
}
double[] probSum = new double[n];
for (int k = 0; k < m; k++)
{
prob[k, 0] /= totalNum;
prob[k, 1] /= totalNum;
prob[k, 2] /= totalNum;
//Sum
probSum[0] += prob[k, 0];
probSum[1] += prob[k, 1];
probSum[2] += prob[k, 2];
if (i == 40 && j == 40) //mapping(INT32)
{
mapping[k, 0] = Convert.ToInt32(255.0 * probSum[0]);
mapping[k, 1] = Convert.ToInt32(255.0 * probSum[1]);
mapping[k, 2] = Convert.ToInt32(255.0 * probSum[2]);
}
}
colors[i, j] = Color.FromArgb(mapping[sourceBmpData[j, i, 0], 0],
mapping[sourceBmpData[j, i, 1], 1],
mapping[sourceBmpData[j, i, 2], 2]);
}
}
return BitmapHelper.CreateBitmap(colors);
}
where BitmapHelper is:
public static class BitmapHelper
{
public struct Pixel : IEquatable
{
// ReSharper disable UnassignedField.Compiler
public byte Blue;
public byte Green;
public byte Red;
public bool Equals(Pixel other)
{
return Red == other.Red && Green == other.Green && Blue == other.Blue;
}
}
public static Color[,] GetPixels(Bitmap two)
{
return ProcessBitmap(two, pixel => Color.FromArgb(pixel.Red, pixel.Green, pixel.Blue));
}
public static float[,] GetBrightness(Bitmap two)
{
return ProcessBitmap(two, pixel => Color.FromArgb(pixel.Red, pixel.Green, pixel.Blue).GetBrightness());
}
public static unsafe T[,] ProcessBitmap<T>(Bitmap bitmap, Func<Pixel, T> func)
{
var lockBits = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly,
bitmap.PixelFormat);
int padding = lockBits.Stride - (bitmap.Width * sizeof(Pixel));
int width = bitmap.Width;
int height = bitmap.Height;
var result = new T[height, width];
var ptr = (byte*)lockBits.Scan0;
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
var pixel = (Pixel*)ptr;
result[i, j] = func(*pixel);
ptr += sizeof(Pixel);
}
ptr += padding;
}
bitmap.UnlockBits(lockBits);
return result;
}
public static Bitmap CreateBitmap(Color[,] colors)
{
const int bytesPerPixel = 4, stride = 8;
int width = colors.GetLength(0);
int height = colors.GetLength(1);
byte[] bytes = new byte[width*height*bytesPerPixel];
int n = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
bytes[n++] = colors[i, j].R;
bytes[n++] = colors[i, j].G;
bytes[n++] = colors[i, j].B;
bytes[n++] = colors[i, j].A;
}
}
return CreateBitmap(bytes, width, height, stride, PixelFormat.Format32bppArgb);
}
public static Bitmap CreateBitmap(byte[] data, int width, int height, int stride, PixelFormat format)
{
var arrayHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
var bmp = new Bitmap(width, height, stride, format, arrayHandle.AddrOfPinnedObject());
arrayHandle.Free();
return bmp;
}
}
I'm trying to output a new image after applying the adaptive median filter but it only works if the maximum window size is 3*3, but it should work for all odd window sizes, and it does so if the image is so small for example 10*10 pixels, but if the image is for example 440*445 pixels it only works if the max window size is 3*3 otherwise it says index outside bounds of the array, here is my code using counting sort to sort the pixels:
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
namespace ImageFilters
{
public class Class2
{
public byte[,] median(byte[,] array, int width, int height, int msize)
{
byte[,] marr = new byte[height + 1, width + 1];
int size = 3;
for (int i = 0; i <height+1; i++)
{
marr[i, 0] = 255;
}
for (int j = 0; j < width+1; j++)
{
marr[0, j] = 255;
}
int n=0;
int z=0;
for (int k = 0; k < (height) * width; k++)
{
repeat:
byte[] farr = new byte[size * size];
int I=0;
for (int i = n; i < n+size; i++)
{
for (int j = z; j < z+size; j++)
{
if (j < width && i < height)
{
farr[I] = array[i, j];
I++;
}
else
{
farr[I] = 0;
I++;
}
}
}
int zxy = farr[(size*size)/2];
byte[] check = new byte[size * size];
check=counting(farr,size);
int median = 0;
int lennn;
lennn = check.Length;
int min = 99999;
int maxi = -1;
int a1, a2;
min = check.Min();
maxi = check.Max();
median = check[lennn / 2];
a1 = median - min;
a2 = maxi - median;
int b1, b2;
b1 = zxy - min;
b2 = maxi - zxy;
if (a1 > 0 && a2 > 0)
{
if (b1 > 0 && b2 > 0)
{
marr[n + 1, z + 1] = Convert.ToByte(zxy);
z++;
if (z + (size - 1) > (width + 1))
{
n++;
z = 0;
}
}
else
{
marr[n + 1, z + 1] = Convert.ToByte(median);
z++;
if (z + (size - 1) > (width + 1))
{
n++;
z = 0;
}
}
}
else
{
size = size + 2;
if (size <= msize)
goto repeat;
else
{
marr[n +1, z +1] = Convert.ToByte(median);
z++;
if (size > 3)
size = 3;
if (z + (size - 1) > (width + 1))
{
n++;
z = 0;
}
}
}
}
return marr;
}
public static byte[] counting(byte[] array1D, int siz)
{
int max = -10000000;
byte[] SortedArray = new byte[siz * siz];
max = array1D.Max();
byte[] Array2 = new byte[max + 1];
//for (int i = 0; i < Array2.Length; i++)
// Array2[i] = 0; // create new array ( Array2) with zeros in every index .
for (int i = 0; i < (siz*siz); i++)
{
Array2[array1D[i]] += 1; //take the element in the index(i) of(array1d) AS the index of (Array2)
// and increment the element in this index by 1 .
}
for (int i = 1; i <= max; i++)
{
Array2[i] += Array2[i - 1]; // Count every element in (array1d) and put it in (Array2).
}
for (int i = (siz*siz) - 1; i >= 0; i--)
{
SortedArray[Array2[array1D[i]] - 1] = array1D[i]; // transfer the element in index (i) of (array1d) to (SortedArray
Array2[array1D[i]]--;
}
return SortedArray;
}
}
}
Any help will be appreciated, thank you.