Set alpha of some pixels to zero - c#

Let's say I have 2 images, one on top of the other. I want to set a portion of the top image's pixel's alpha values to zero so I get this effect illustrated below:
The brown is the top image, and the blue water is the bottom image. Ultimately what I'm going to do is have the alpha channel of the top image's pixels change to zero as the user touches the iPad screen so they can essentially draw with their fingertip and have the blue water image appear.
I have a function that can examine the image counting it's pixels whose alpha is equal to zero. I also have a function that Sets the alpha of a pixel to zero but I think it doesn't work. I can read the alpha value, and set the alpha value. Then I can re-read the alpha value to confirm that it was set. But when I set the new image I can't see a difference!
Here is a dropbox link to my example project:
https://www.dropbox.com/s/e93hzxl5ru5wnss/TestAlpha.zip
And below is the source:
Water = new UIImageView(this.Bounds);
Water_OriginalImage = UIImage.FromFile("Media/Images/Backgrounds/WaterTexture.png");
Water.Image = Water_OriginalImage;
this.AddSubview(Water);
Dirt = new UIImageView(this.Bounds);
Dirt_ModifiableImage = UIImage.FromFile("Media/Images/Backgrounds/DirtBackground.png");
Dirt.Image = null;
this.AddSubview(Dirt);
CGImage image = Dirt_ModifiableImage.CGImage;
int width = image.Width;
int height = image.Height;
CGColorSpace colorSpace = image.ColorSpace;
int bytesPerRow = image.BytesPerRow;
int bitmapByteCount = bytesPerRow * height;
int bitsPerComponent = image.BitsPerComponent;
CGImageAlphaInfo alphaInfo = image.AlphaInfo;
// Allocate memory because the BitmapData is unmanaged
IntPtr BitmapData = Marshal.AllocHGlobal(bitmapByteCount);
CGBitmapContext context = new CGBitmapContext(BitmapData, width, height, bitsPerComponent, bytesPerRow, colorSpace, alphaInfo);
context.SetBlendMode(CGBlendMode.Copy);
context.DrawImage(new RectangleF(0, 0, width, height), image);
// Console output from this function call says "alpha count = 0"
CountZeroedAlphas(BitmapData, Dirt_ModifiableImage);
RemoveSomeAlpha(BitmapData, Dirt_ModifiableImage);
// Console output from this function call says "alpha count = 2000". So it seems that I have set the alpha of 2000 pixels to zero...
CountZeroedAlphas(BitmapData, Dirt_ModifiableImage);
// Set the Dirt Image equal to the context image, but I still see all top image, I dont' see any bottom image showing through.
Dirt.Image = UIImage.FromImage (context.ToImage());
// Free memory used by the BitmapData now that we're finished
Marshal.FreeHGlobal(BitmapData);
public void CountZeroedAlphas( IntPtr bitmapData, UIImage Image )
{
int widthIndex = 0;
int heightIndex = 0;
int count = 0;
while ( widthIndex < Image.Size.Width )
{
while ( heightIndex < Image.Size.Height )
{
PointF point = new PointF(widthIndex, heightIndex);
var startByte = (int) ((point.Y * Image.Size.Width + point.X) * 4);
byte alpha = GetByte(startByte, bitmapData);
if ( alpha == 0 )
count++;
heightIndex++;
}
widthIndex++;
}
Console.WriteLine("alpha count = " + count);
}
public void RemoveSomeAlpha( IntPtr bitmapData, UIImage Image )
{
int widthIndex = 0;
int heightIndex = 0;
while ( widthIndex < Image.Size.Width )
{
while ( heightIndex < Image.Size.Height )
{
PointF point = new PointF(widthIndex, heightIndex);
var startByte = (int) ((point.Y * Image.Size.Width + point.X) * 4);
ZeroByte(startByte, bitmapData);
heightIndex++;
}
widthIndex++;
}
}
public unsafe byte GetByte(int offset, IntPtr buffer)
{
byte* bufferAsBytes = (byte*) buffer;
return bufferAsBytes[offset];
}
public unsafe void ZeroByte(int offset, IntPtr buffer)
{
byte* bufferAsBytes = (byte*) buffer;
bufferAsBytes[offset] = 0;
}

Got it working! I'm not sure what the problem was exactly (assuming I was accessing the wrong bytes on accident or something), but I have the working code.
This was extremely useful:
// Create layers of matter on the battlefield. Example: Grass, dirt, water
Water = new UIImageView(UIScreen.MainScreen.Bounds);
Water_OriginalImage = UIImage.FromFile("WaterTexture.png");
Water.Image = Water_OriginalImage;
this.View.AddSubview(Water);
Dirt = new UIImageView(UIScreen.MainScreen.Bounds);
Dirt_ModifiableImage = UIImage.FromFile("DirtBackground.png");
Dirt.Image = null;
this.View.AddSubview(Dirt);
CGImage image = Dirt_ModifiableImage.CGImage;
int width = image.Width;
int height = image.Height;
CGColorSpace colorSpace = image.ColorSpace;
int bytesPerRow = image.BytesPerRow;
// int bytesPerPixel = 4;
int bytesPerPixel = bytesPerRow / width;
int bitmapByteCount = bytesPerRow * height;
int bitsPerComponent = image.BitsPerComponent;
CGImageAlphaInfo alphaInfo = image.AlphaInfo;
// Allocate memory because the BitmapData is unmanaged
IntPtr BitmapData = Marshal.AllocHGlobal(bitmapByteCount);
CGBitmapContext context = new CGBitmapContext(BitmapData, width, height, bitsPerComponent, bytesPerRow, colorSpace, alphaInfo);
context.SetBlendMode(CGBlendMode.Copy);
context.DrawImage(new RectangleF(0, 0, width, height), image);
int tempX = 0;
while ( tempX < Dirt_ModifiableImage.Size.Width )
{
int tempY = 0;
while ( tempY < Dirt_ModifiableImage.Size.Height )
{
int byteIndex = (bytesPerRow * tempY) + tempX * bytesPerPixel;
ZeroByte(byteIndex+3, BitmapData);
byte red = GetByte(byteIndex, BitmapData);
byte green = GetByte(byteIndex+1, BitmapData);
byte blue = GetByte(byteIndex+2, BitmapData);
byte alpha = GetByte(byteIndex+3, BitmapData);
//Console.WriteLine("red = " + red + " green = " + green + " blue = " + blue + " alpha = " + alpha);
tempY++;
}
tempX++;
}
NSData newImageData = NSData.FromBytes(BitmapData, (uint)(bitmapByteCount));
Dirt.Image = UIImage.LoadFromData(newImageData);

Related

Send Arabic text as Bitmap

I want to Send Arabic text as Bitmap to a POS printer since I could not print Arabic words directly to the printer. I used below code to convert a text to Bitmap :
Convert_ValueToImage("كيكه", "Simplified Arabic Fixed", 12)
public static Bitmap Convert_ValueToImage(string ValueText, string Fontname, int Fontsize)
{
//creating bitmap image
Bitmap ValueBitmap = new Bitmap(1, 1);
//FromImage method creates a new Graphics from the specified Image.
Graphics Graphics = Graphics.FromImage(ValueBitmap);
// Create the Font object for the image text drawing.
Font Font = new Font(Fontname, Fontsize);
// Instantiating object of Bitmap image again with the correct size for the text and font.
SizeF stringSize = Graphics.MeasureString(ValueText, Font);
ValueBitmap = new Bitmap(ValueBitmap, (int)stringSize.Width, (int)stringSize.Height);
Graphics = Graphics.FromImage(ValueBitmap);
//Draw Specified text with specified format
Graphics.DrawString(ValueText, Font, Brushes.Black, 0, 0);
Font.Dispose();
Graphics.Flush();
Graphics.Dispose();
return ValueBitmap; //return Bitmap Image
}
and when I assign it to pictureBox it works.
Now I want to send it to the printer. I used below method to convert the bitmap image to string with adding the image mode to the string:
public string GetArabic(Bitmap ArabicText)
{
string logo = "";
BitmapData data = GetArabicBitmapData(ArabicText);
BitArray dots = data.Dots;
byte[] width = BitConverter.GetBytes(data.Width);
int offset = 0;
MemoryStream stream = new MemoryStream();
BinaryWriter bw = new BinaryWriter(stream);
bw.Write((char)0x1B);
bw.Write('#');
bw.Write((char)0x1B);
bw.Write('3');
bw.Write((byte)24);
while (offset < data.Height)
{
bw.Write((char)0x1B);
bw.Write('*'); // bit-image mode
bw.Write((byte)33); // 24-dot double-density
bw.Write(width[0]); // width low byte
bw.Write(width[1]); // width high byte
for (int x = 0; x < data.Width; ++x)
{
for (int k = 0; k < 3; ++k)
{
byte slice = 0;
for (int b = 0; b < 8; ++b)
{
int y = (((offset / 8) + k) * 8) + b;
// Calculate the location of the pixel we want in the bit array.
// It'll be at (y * width) + x.
int i = (y * data.Width) + x;
// If the image is shorter than 24 dots, pad with zero.
bool v = false;
if (i < dots.Length)
{
v = dots[i];
}
slice |= (byte)((v ? 1 : 0) << (7 - b));
}
bw.Write(slice);
}
}
offset += 24;
bw.Write((char)0x0A);
}
// Restore the line spacing to the default of 30 dots.
bw.Write((char)0x1B);
bw.Write('3');
bw.Write((byte)30);
bw.Flush();
byte[] bytes = stream.ToArray();
return logo + Encoding.Default.GetString(bytes);
}
public BitmapData GetArabicBitmapData(Bitmap bmpFileName)
{
using (var bitmap = bmpFileName )
{
var threshold = 127;
var index = 0;
double multiplier = 570; // this depends on your printer model. for Beiyang you should use 1000
double scale = (double)(multiplier / (double)bitmap.Width);
int xheight = (int)(bitmap.Height * scale);
int xwidth = (int)(bitmap.Width * scale);
var dimensions = xwidth * xheight;
var dots = new BitArray(dimensions);
for (var y = 0; y < xheight; y++)
{
for (var x = 0; x < xwidth; x++)
{
var _x = (int)(x / scale);
var _y = (int)(y / scale);
var color = bitmap.GetPixel(_x, _y);
var luminance = (int)(color.R * 0.3 + color.G * 0.59 + color.B * 0.11);
dots[index] = (luminance < threshold);
index++;
}
}
return new BitmapData()
{
Dots = dots,
Height = (int)(bitmap.Height * scale),
Width = (int)(bitmap.Width * scale)
};
}
}
this code print a black Rectangle. what would help me is if I could print the text with white background and the size is small as the text size.

C# RGB[,] to picturebox

I have a data that is (2448*2048) 5Mpixel image data, but the picturebox only has (816*683) about 500,000 pixels, so I lowered the pixels and I only need a black and white image, so I used the G value to create the image, but The image I output is shown in the following figure. Which part of my mistake?
public int[,] lowered(int[,] greenar)
{
int[,] Sy = new int[816, 683];
int x = 0;
int y = 0;
for (int i = 1; i < 2448; i += 3)
{
for (int j = 1; j < 2048; j += 3)
{
Sy[x, y] = greenar[i, j];
y++;
}
y = 0;
x++;
}
return Sy;
}
static Bitmap Create(int[,] R, int[,] G, int[,] B)
{
int iWidth = G.GetLength(1);
int iHeight = G.GetLength(0);
Bitmap Result = new Bitmap(iWidth, iHeight,
System.Drawing.Imaging.PixelFormat.Format24bppRgb);
Rectangle rect = new Rectangle(0, 0, iWidth, iHeight);
System.Drawing.Imaging.BitmapData bmpData = Result.LockBits(rect,
System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
IntPtr iPtr = bmpData.Scan0;
int iStride = bmpData.Stride;
int iBytes = iWidth * iHeight * 3;
byte[] PixelValues = new byte[iBytes];
int iPoint = 0;
for (int i = 0; i < iHeight; i++)
{
for (int j = 0; j < iWidth; j++)
{
int iG = G[i, j];
int iB = G[i, j];
int iR = G[i, j];
PixelValues[iPoint] = Convert.ToByte(iB);
PixelValues[iPoint + 1] = Convert.ToByte(iG);
PixelValues[iPoint + 2] = Convert.ToByte(iR);
iPoint += 3;
}
}
System.Runtime.InteropServices.Marshal.Copy(PixelValues, 0, iPtr, iBytes);
Result.UnlockBits(bmpData);
return Result;
}
https://upload.cc/i1/2018/04/26/WHOXTJ.png
You don't need to downsample your image, you can do it in this way. Set picturebox property BackgroundImageLayout as either zoom or stretch and assign it as:
picturebox.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom;
picturebox.BackgroundImage = bitmap;
System.Windows.Forms.ImageLayout.Zoom will automatically adjust your bitmap to the size of picturebox.
You seem to be constantly mixing up your x and y offsets, which can easily be avoided simply by actually calling your loop variables x and y whenever you loop through image data. Also, image data is generally saved line by line, so your outer loop should be the Y loop going over the height, and the inner loop should process the X coordinates on one line, and should thus loop over the width.
Also, I'm not sure where your original data comes from, but in most of the cases I've seen where the image data is in multidimensional arrays like this, the Y is actually the first index in the array. Your actual image building function also assumes this, since it uses G.GetLength(0) to get the height of the image. But your channel resize function doesn't; it makes a multidimensional array as new int[816, 683], which would be a 683*816 image, not 816*683 as you said. So that certainly seems wrong.
Since you confirmed it to be [x,y], I adapted this solution to use it like that.
That aside, you hardcoded a lot of values in your functions, which is very bad practice. If you know you will reduce the image to 1/3rd by taking only one in three pixels, just give that 3 as parameter.
The reduction code:
public static Int32[,] ResizeChannel(Int32[,] origChannel, Int32 lossfactor)
{
Int32 newWidth = origChannel.GetLength(0) / lossfactor;
Int32 newHeight = origChannel.GetLength(1) / lossfactor;
// to avoid rounding errors
Int32 origHeight = newHeight * lossfactor;
Int32 origWidth = newWidth *lossfactor;
Int32[,] newChannel = new Int32[newWidth, newHeight];
Int32 newX = 0;
Int32 newY = 0;
for (Int32 y = 1; y < origHeight; y += lossfactor)
{
newX = 0;
for (Int32 x = 1; x < origWidth; x += lossfactor)
{
newChannel[newX, newY] = origChannel[x, y];
newX++;
}
newY++;
}
return newChannel;
}
The actual build code, as was remarked by GSerg in the comments, is wrong because you don't take the stride into account. The stride is the actual byte length of each line of pixels, and this is not just width * BytesPerPixel, since it gets rounded up to the next multiple of 4 bytes.
So you need to initialize your array as height * stride, not as height * width * 3, and you need to skip your write offset to the next multiple of the stride whenever you go to a lower Y line, rather than assuming it will just get there automatically because your X processing adds 3 for each pixel. Because it will not get there automatically, unless, by pure coincidence, your image width happens to be a multiple of 4 pixels.
Also, if you only use one channel for this, there is no reason to give it all three channels. Just give a single one.
public static Bitmap CreateGreyImage(Int32[,] greyChannel)
{
Int32 width = greyChannel.GetLength(0);
Int32 height = greyChannel.GetLength(1);
Bitmap result = new Bitmap(width, height, PixelFormat.Format24bppRgb);
Rectangle rect = new Rectangle(0, 0, width, height);
BitmapData bmpData = result.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
Int32 stride = bmpData.Stride;
// stride is the actual line width in bytes.
Int32 bytes = stride * height;
Byte[] pixelValues = new Byte[bytes];
Int32 offset = 0;
for (Int32 y = 0; y < height; y++)
{
Int32 workOffset = offset;
for (Int32 x = 0; x < width; x++)
{
pixelValues[workOffset + 0] = (Byte)greyChannel[x, y];
pixelValues[workOffset + 1] = (Byte)greyChannel[x, y];
pixelValues[workOffset + 2] = (Byte)greyChannel[x, y];
workOffset += 3;
}
// Add stride to get the start offset of the next line
offset += stride;
}
Marshal.Copy(pixelValues, 0, bmpData.Scan0, bytes);
result.UnlockBits(bmpData);
return result;
}
Now, this works as expected if your R, G and B channels are indeed identical, But if they are not, you have to realize there is a difference between reducing the image to grayscale and just building a grey image from the green channel. On a colour image, you will get totally different results if you take the blue or red channel instead.
This was the code I executed for this:
Int32[,] greyar = ResizeChannel(greenar, 3);
Bitmap newbm = CreateGreyImage(greyar);

Convert an image to grayscale parallel loop

I have written a code that converts image to grayscale. but the code only convert partial of it.
I am trying to convert this code to a Parallel computation. I end up with bugs that I can not get my head around them. Any suggestion?
private void button2_Click(object sender, EventArgs e)
{
Bitmap bmp = (Bitmap)pictureBox1.Image;
unsafe {
//get image dimension
//int width = bmp.Width;
//int height = bmp.Height;
BitmapData bitmapData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, bmp.PixelFormat);
//define variable
int bpp = System.Drawing.Bitmap.GetPixelFormatSize(bmp.PixelFormat) / 8;
int hip = bitmapData.Height;
int wib = bitmapData.Width + bpp;
//point to first pixel
byte* PtrFirstPixel = (byte*)bitmapData.Scan0;
//color of pixel
// Color p;
//grayscale
Parallel.For(0, hip, y =>
{
byte* currentLine = PtrFirstPixel + (y * bitmapData.Stride);
for (int x = 0; x < wib; x = x + bpp)
{
//get pixel value
//p = bmp.GetPixel(x, y);
//extract pixel component ARGB
//int a = p.A;
//int r = p.R;
//int g = p.G;
// int b = p.B;
int b = currentLine[x];
int g = currentLine[x + 1];
int r = currentLine[x + 2];
//find average
int avg = (r + g + b) / 3;
//set new pixel value
// bmp.SetPixel(x, y, Color.FromArgb(a, avg, avg, avg));
currentLine[x] = (byte)avg;
currentLine[x + 1] = (byte)avg;
currentLine[x + 2] = (byte)avg;
}
});
bmp.UnlockBits(bitmapData);
//load grayscale image in picturebox2
//pictureBox2.Image = bmp;
}
pictureBox2.Image = bmp;
}
my out put image
int wib = bitmapData.Width + bpp;
should be:
int wib = bitmapData.Width * bpp;
You want the number of bytes which requires a multiply, not an add. There may be other issues, but this is definitely incorrect.

Color to Bitmap

How do I go about setting a Bitmap with a Color of the pixels. I created a program with LockBits and it is very fast but now I need to set a PictureBox with that image I ran through the LockBits I do not want to use SetPixels My current code is:
Bitmap imageFile = new Bitmap(bmpPath);
BitmapData imageData = imageFile.LockBits(new Rectangle(0, 0, imageFile.Width, imageFile.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
IntPtr Pointer = imageData.Scan0;
int ArraySize = Math.Abs(imageData.Stride) * imageFile.Height;
byte[] PixelArray = new byte[ArraySize];
Marshal.Copy(Pointer, PixelArray, 0, ArraySize);
int PixelAmount = 4; //ArGb
Color ArGBformat;
Bitmap RenderedImage = new Bitmap(imageFile.Width, imageFile.Height);
byte NewAlpha;
byte NewRed;
byte NewGreen;
byte NewBlue;
unsafe
{
for (int y = 0; y < imageData.Height; y++)
{
byte* row = (byte*)imageData.Scan0 + (y * imageData.Stride);
for (int x = 0; x < imageData.Width; x++)
{
int offSet = x * PixelAmount;
// read pixels
byte blue = row[offSet];
byte green = row[offSet + 1];
byte red = row[offSet + 2];
byte alpha = row[offSet + 3];
//Manipulates pixels
NewAlpha = Convert.ToByte(Math.Abs(alpha - _Alpha));
NewRed = Convert.ToByte(Math.Abs(red - _Red));
NewBlue = Convert.ToByte(Math.Abs(blue - _Blue));
NewGreen = Convert.ToByte(Math.Abs(green - _Green));
ArGBformat = Color.FromArgb(NewAlpha, NewRed, NewGreen, NewBlue);
RenderedImage.SetPixel(x, y, ArGBformat); //Slow and want something else
}
}
}
I would like to set my PictureBox1 to the pixels that get ran through the program.
Found the answer. I needed to set the pixels back.
//Sets image
row[offSet] = NewBlue;
row[offSet + 1] = NewGreen;
row[offSet + 2] = NewRed;
row[offSet + 3] = NewAlpha;

Lockbits stride on 1bpp Indexed image byte boundaries

I am cutting and pasting from one 1bpp indexed image to a new image.
All works well until the starting pixel is a divisor of 8. In the code below stride is equal to a value relative to the width of the rectangle until I hit a byte boundary. Then the stride is equal to the width of the entire page.
var croppedRect = new Rectangle((int)left, (int)top, (int)width, (int)height);
BitmapData croppedSource = _bitmapImage.LockBits(croppedRect, ImageLockMode.ReadWrite, BitmapImage.PixelFormat);
int stride = croppedSource.Stride;
This is a problem because rather than pasting my selected area into the new image, the Marshal copies a cross section, the height of the selected area, of the entire width of the page.
int numBytes = stride * (int)height;
var srcData = new byte[numBytes];
Marshal.Copy(croppedSource.Scan0, srcData, 0, numBytes);
Marshal.Copy(srcData, 0, croppedDest.Scan0, numBytes);
destBmp.UnlockBits(croppedDest);
Here's my code for anyone who's interested. There may be a more optimal solution but this works. I am creating an entire page in white and duplicating the selected area in the new page as I pass over it. Thanks to Bob Powell for the SetIndexedPixel routine.
protected int GetIndexedPixel(int x, int y, BitmapData bmd)
{
var index = y * bmd.Stride + (x >> 3);
var p = Marshal.ReadByte(bmd.Scan0, index);
var mask = (byte)(0x80 >> (x & 0x7));
return p &= mask;
}
protected void SetIndexedPixel(int x, int y, BitmapData bmd, bool pixel)
{
int index = y * bmd.Stride + (x >> 3);
byte p = Marshal.ReadByte(bmd.Scan0, index);
byte mask = (byte)(0x80 >> (x & 0x7));
if (pixel)
p &= (byte)(mask ^ 0xff);
else
p |= mask;
Marshal.WriteByte(bmd.Scan0, index, p);
}
public DocAppImage CutToNew(int left, int top, int width, int height, int pageWidth, int pageHeight)
{
var destBmp = new Bitmap(pageWidth, pageHeight, BitmapImage.PixelFormat);
var pageRect = new Rectangle(0, 0, pageWidth, pageHeight);
var pageData = destBmp.LockBits(pageRect, ImageLockMode.WriteOnly, BitmapImage.PixelFormat);
var croppedRect = new Rectangle(left, top, width, height);
var croppedSource = BitmapImage.LockBits(croppedRect, ImageLockMode.ReadWrite, BitmapImage.PixelFormat);
for (var y = 0; y < pageHeight; y++)
for (var x = 0; x < pageWidth; x++)
{
if (y >= top && y <= top + height && x >= left && x <= width + left)
{
SetIndexedPixel(x, y, pageData,
GetIndexedPixel(x - left, y - top, croppedSource) == 0 ? true : false);
SetIndexedPixel(x - left, y - top, croppedSource, false); //Blank area in original
}
else
SetIndexedPixel(x, y, pageData, false); //Fill the remainder of the page with white.
}
destBmp.UnlockBits(pageData);
var retVal = new DocAppImage { BitmapImage = destBmp };
destBmp.Dispose();
BitmapImage.UnlockBits(croppedSource);
SaveBitmapToFileImage(BitmapImage);
return retVal;
}

Categories