Related
I am using ExtTextOut wingdi function to draw text. Also, I am using SetWorldTransform for scaling text but now I want to rotate text(at all angles) too. How can I change/pass parameters to it so it will scale as well as rotate too?
Below is the sample code block, this code works perfect but now I want to introduce rotating text as well without impacting scaling.
var inputString = "Sample text";
var fontSize = 31f;
var font = new Font("Avenir Black", fontSize, new FontStyle());
var startX1 = 50;
var startY1 = 50;
LOGFONT lf = new LOGFONT();
font.ToLogFont(lf, e.Graphics);
IntPtr hPrinterDC = e.Graphics.GetHdc();
StringBuilder sbText = new StringBuilder(inputString);
if (hPrinterDC != IntPtr.Zero)
{
int nPrintHorzRes = GetDeviceCaps(hPrinterDC, HORZRES);
int nPrintVertRes = GetDeviceCaps(hPrinterDC, VERTRES);
int nPhysWidth = GetDeviceCaps(hPrinterDC, PHYSICALWIDTH);
int nPhysHeight = GetDeviceCaps(hPrinterDC, PHYSICALHEIGHT);
int nPhysOffsetX = GetDeviceCaps(hPrinterDC, PHYSICALOFFSETX);
int nPhysOffsetY = GetDeviceCaps(hPrinterDC, PHYSICALOFFSETY);
IntPtr hDCScreen = GetDC(IntPtr.Zero);
float nLogPixelsXScreen = GetDeviceCaps(hDCScreen, LOGPIXELSX);
float nLogPixelsYScreen = GetDeviceCaps(hDCScreen, LOGPIXELSY);
ReleaseDC(hDCScreen, IntPtr.Zero);
float nLogPixelsXPrinter = GetDeviceCaps(hPrinterDC, LOGPIXELSX);
float nLogPixelsYPrinter = GetDeviceCaps(hPrinterDC, LOGPIXELSY);
float nScaleX = Math.Max(nLogPixelsXScreen, nLogPixelsXPrinter) / Math.Min(nLogPixelsXScreen, nLogPixelsXPrinter);
float nScaleY = Math.Max(nLogPixelsYScreen, nLogPixelsYPrinter) / Math.Min(nLogPixelsYScreen, nLogPixelsYPrinter);
System.Drawing.Drawing2D.Matrix transform = new System.Drawing.Drawing2D.Matrix();
transform.Scale(nScaleX, nScaleY);
XFORM renderTransform = new XFORM();
var elements = transform.Elements;
var m11 = elements[0];
var m12 = elements[1];
var m21 = elements[2];
var m22 = elements[3];
var dx = elements[4];
var dy = elements[5];
renderTransform.eM11 = (float)m11;
renderTransform.eM12 = (float)m12;
renderTransform.eM21 = (float)m21;
renderTransform.eM22 = (float)m22;
int nOffsetX = 0;
int nOffsetY = 0;
renderTransform.eDx = (float)transform.OffsetX + nOffsetX;
renderTransform.eDy = (float)transform.OffsetY + nOffsetY;
SetGraphicsMode(hPrinterDC, GM_ADVANCED);
SetMapMode(hPrinterDC, MM_TEXT);
bool bRet = SetWorldTransform(hPrinterDC, ref renderTransform);
var startX2 = startX1;
var startY2 = startY1;
RECT rc = new RECT(startX2, startY2, nPhysWidth, nPhysHeight);
SetViewportOrgEx(hPrinterDC, -nPhysOffsetX, -nPhysOffsetY, IntPtr.Zero);
lf.lfHeight = (int)(lf.lfHeight / nScaleY);
IntPtr hFontNew = CreateFontIndirect(lf);
IntPtr hFontOld = SelectObject(hPrinterDC, hFontNew);
SetBkMode(hPrinterDC, TRANSPARENT);
SetTextColor(hPrinterDC, ColorTranslator.ToWin32(Color.Blue));
ExtTextOut(hPrinterDC, rc.left, rc.top, ETO_OPAQUE, IntPtr.Zero, sbText, (uint)sbText.Length, IntPtr.Zero);
rc.top += -lf.lfHeight;
SetTextColor(hPrinterDC, ColorTranslator.ToWin32(Color.Green));
ExtTextOut(hPrinterDC, rc.left, rc.top, ETO_OPAQUE, IntPtr.Zero, sbText, (uint)sbText.Length, IntPtr.Zero);
var nExtra = -10.0f / nScaleX;
SetTextCharacterExtra(hPrinterDC, (int)nExtra);
rc.top += -lf.lfHeight;
SetTextColor(hPrinterDC, ColorTranslator.ToWin32(Color.Red));
ExtTextOut(hPrinterDC, rc.left, rc.top, ETO_OPAQUE, IntPtr.Zero, sbText, (uint)sbText.Length, IntPtr.Zero);
SelectObject(hPrinterDC, hFontOld);
DeleteObject(hFontNew);
}
Here are pinvokes:
[DllImport("Gdi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool SetWorldTransform(IntPtr hdc, ref XFORM lpxf);
[StructLayout(LayoutKind.Sequential)]
public struct XFORM
{
public float eM11;
public float eM12;
public float eM21;
public float eM22;
public float eDx;
public float eDy;
}
[DllImport("Gdi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern int SetGraphicsMode(IntPtr hdc, int iMode);
public const int GM_COMPATIBLE = 1;
public const int GM_ADVANCED = 2;
public const int GM_LAST = 2;
[DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
public const int TRANSPARENT = 1;
public const int OPAQUE = 2;
public const int BKMODE_LAST = 2;
[DllImport("Gdi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern int SetBkMode(IntPtr hdc, int mode);
Okay, I am looking for a function or something that will read the color of a certain pixel on my monitor, and when that color is detected, another function will be enabled. I figure using RGB. All help appreciated. Thank You.
This is the most efficient: It grabs a pixel at the location of the cursor, and doesn't rely on only having one monitor.
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Diagnostics;
namespace FormTest
{
public partial class Form1 : Form
{
[DllImport("user32.dll")]
static extern bool GetCursorPos(ref Point lpPoint);
[DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
public static extern int BitBlt(IntPtr hDC, int x, int y, int nWidth, int nHeight, IntPtr hSrcDC, int xSrc, int ySrc, int dwRop);
public Form1()
{
InitializeComponent();
}
private void MouseMoveTimer_Tick(object sender, EventArgs e)
{
Point cursor = new Point();
GetCursorPos(ref cursor);
var c = GetColorAt(cursor);
this.BackColor = c;
if (c.R == c.G && c.G < 64 && c.B > 128)
{
MessageBox.Show("Blue");
}
}
Bitmap screenPixel = new Bitmap(1, 1, PixelFormat.Format32bppArgb);
public Color GetColorAt(Point location)
{
using (Graphics gdest = Graphics.FromImage(screenPixel))
{
using (Graphics gsrc = Graphics.FromHwnd(IntPtr.Zero))
{
IntPtr hSrcDC = gsrc.GetHdc();
IntPtr hDC = gdest.GetHdc();
int retval = BitBlt(hDC, 0, 0, 1, 1, hSrcDC, location.X, location.Y, (int)CopyPixelOperation.SourceCopy);
gdest.ReleaseHdc();
gsrc.ReleaseHdc();
}
}
return screenPixel.GetPixel(0, 0);
}
}
}
Now, obviously, you don't have to use the cursor's current location, but this is the general idea.
EDIT:
Given the above GetColorAt function you can poll a certain pixel on the screen in a safe, performance friendly way like this:
private void PollPixel(Point location, Color color)
{
while(true)
{
var c = GetColorAt(location);
if (c.R == color.R && c.G == color.G && c.B == color.B)
{
DoAction();
return;
}
// By calling Thread.Sleep() without a parameter, we are signaling to the
// operating system that we only want to sleep long enough for other
// applications. As soon as the other apps yield their CPU time, we will
// regain control.
Thread.Sleep()
}
}
You can wrap that in a Thread if you want, or execute it from a Console application. "Whatever suits your fancy," I guess.
Most answers here use the very same source of that pixel (desktop dc).
The key function is GetPixel.
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetWindowDC(IntPtr window);
[DllImport("gdi32.dll", SetLastError = true)]
public static extern uint GetPixel(IntPtr dc, int x, int y);
[DllImport("user32.dll", SetLastError = true)]
public static extern int ReleaseDC(IntPtr window, IntPtr dc);
public static Color GetColorAt(int x, int y)
{
IntPtr desk = GetDesktopWindow();
IntPtr dc = GetWindowDC(desk);
int a = (int) GetPixel(dc, x, y);
ReleaseDC(desk, dc);
return Color.FromArgb(255, (a >> 0) & 0xff, (a >> 8) & 0xff, (a >> 16) & 0xff);
}
I think this is the cleanest and quickest way.
Note:
If you have modified the default text size among the Display Settings on Windows to increase readability on a high resolution display, the coordinate parameters of GetPixel() need to be adjusted the same way. For example, if the cursor location is (x,y) with 150% of text size on Windows 7, you need to call GetPixel(x*1.5, y*1.5) to get the color of the pixel under the cursor.
This function is shorter and can achieve the same result using System.Drawing, without Pinvoke.
Color GetColorAt(int x, int y)
{
Bitmap bmp = new Bitmap(1, 1);
Rectangle bounds = new Rectangle(x, y, 1, 1);
using (Graphics g = Graphics.FromImage(bmp))
g.CopyFromScreen(bounds.Location, Point.Empty, bounds.Size);
return bmp.GetPixel(0, 0);
}
Please check this two different functions I have used in one of my previous projects :
1) This function takes snapshot of Desktop
private void CaptureScreenAndSave(string strSavePath)
{
//SetTitle("Capturing Screen...");
Bitmap bmpScreenshot;
Graphics gfxScreenshot;
bmpScreenshot = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height,System.Drawing.Imaging.PixelFormat.Format32bppArgb);
gfxScreenshot = Graphics.FromImage(bmpScreenshot);
gfxScreenshot.CopyFromScreen(Screen.PrimaryScreen.Bounds.X, Screen.PrimaryScreen.Bounds.Y, 0, 0, Screen.PrimaryScreen.Bounds.Size, CopyPixelOperation.SourceCopy);
MemoryStream msIn = new MemoryStream();
bmpScreenshot.Save(msIn, System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders()[0], null);
msIn.Close();
byte[] buf = msIn.ToArray();
MemoryStream msOut = new MemoryStream();
msOut.Write(buf, 0, buf.Length);
msOut.Position = 0;
Bitmap bmpOut = new Bitmap(msOut);
try
{
bmpOut.Save(strSavePath, System.Drawing.Imaging.ImageFormat.Bmp);
//SetTitle("Capturing Screen Image Saved...");
}
catch (Exception exp)
{
}
finally
{
msOut.Close();
}
}
2) This function takes an image in input and calculates RGB average of pixel range given.
double GetRGBAverageForPixelRange( int istartRange, int iEndRange, Bitmap oBitmap )
{
double dRetnVal = 0 ;
Color oTempColor ;
int i, j ;
for( int iCounter = istartRange ; iCounter < iEndRange ; iCounter++ )
{
i = (iCounter % (oBitmap.Width));
j = ( iCounter / ( oBitmap.Width ) ) ;
if (i >= 0 && j >= 0 && i < oBitmap.Width && j < oBitmap.Height )
{
oTempColor = oBitmap.GetPixel(i, j);
dRetnVal = dRetnVal + oTempColor.ToArgb();
}
}
return dRetnVal ;
}
This two functions together might solve your problem. Happy Coding :)
EDIT : Please note that GetPixel is very slow function. I will think twice befor using it.
As far as I know the easiest way to do this is to:
take a screenshot
look at the bitmap and get the pixel color
Edit
There is probably no way to "wait" until the pixel changes to a certain color. Your program will probably have to just loop and check it every so often until it sees the color.
For example:
while(!IsPixelColor(x, y, color))
{
//probably best to add a sleep here so your program doesn't use too much CPU
}
DoAction();
EDIT 2
Here is some sample code you can modify. This code just changes the color of a label based on the current color in a given pixel. This code avoids the handle leak mentioned.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Runtime.InteropServices;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
[DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
public static extern int BitBlt(IntPtr hDC, int x, int y, int nWidth, int nHeight, IntPtr hSrcDC, int xSrc, int ySrc, int dwRop);
Thread t;
int x, y;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
x = 20;
y = 50;
t = new Thread(update);
t.Start();
}
private void update()
{
Bitmap screenCopy = new Bitmap(1, 1);
using (Graphics gdest = Graphics.FromImage(screenCopy))
{
while (true)
{
//g.CopyFromScreen(new Point(0, 0), new Point(0, 0), new Size(256, 256));
using (Graphics gsrc = Graphics.FromHwnd(IntPtr.Zero))
{
IntPtr hSrcDC = gsrc.GetHdc();
IntPtr hDC = gdest.GetHdc();
int retval = BitBlt(hDC, 0, 0, 1, 1, hSrcDC, x, y, (int)CopyPixelOperation.SourceCopy);
gdest.ReleaseHdc();
gsrc.ReleaseHdc();
}
Color c = Color.FromArgb(screenCopy.GetPixel(0, 0).ToArgb());
label1.ForeColor = c;
}
}
}
}
}
This line uses About 10 ms.
int retval = BitBlt(hDC, 0, 0, 1, 1, hSrcDC, location.X, location.Y, (int)CopyPixelOperation.SourceCopy);
I'm currently recreating my Image Explorer application, formerly written in Windows Forms to the Windows Presentation Framework.
My WinForms application was using the WindowsThumbnailProvider from #DanielPeñalba (See this link for the original version of the code)
WinForms Version - Successfully converting 0 alpha, 0 red, 0 green and 0 blue to Transparent
WPF Version - Almost working
WPF Code - Slightly modified version of the original WindowsThumbnailProvider to support System.Windows.Media.Imaging.BitmapImage instead of System.Drawing.Bitmap
MainWindow.xaml - For all the testing
<Window x:Class="WpfFileFolderThumbnails.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfFileFolderThumbnails"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="600">
<Grid>
<Image x:Name="ThumbnailImage1" HorizontalAlignment="Left" Height="256" Margin="10,20,0,0" VerticalAlignment="Top" Width="256"/>
<Image x:Name="ThumbnailImage2" HorizontalAlignment="Left" Height="256" Margin="326,20,0,0" VerticalAlignment="Top" Width="256"/>
</Grid>
</Window>
MainWindow.xaml.cs - Test code to call the GetThumbnail and CreateAlphaBitmapImage methods
using System.Windows;
namespace WpfFileFolderThumbnails
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
var thumbnail = WindowsThumbnailProviderWpf.GetThumbnail(#"D:\Pictures\Art\Anime", 256, 256,
ThumbnailOptions.ThumbnailOnly);
var alphaThumbnail = WindowsThumbnailProviderWpf.CreateAlphaBitmapImage(thumbnail);
this.ThumbnailImage1.Source = thumbnail;
this.ThumbnailImage2.Source = alphaThumbnail;
}
}
}
WindowsThumbnailProviderWpf.cs - Class to get Folder Thumbnail and make it Transparent
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Path = System.IO.Path;
namespace WpfFileFolderThumbnails
{
[Flags]
public enum ThumbnailOptions
{
None = 0x00,
BiggerSizeOk = 0x01,
InMemoryOnly = 0x02,
IconOnly = 0x04,
ThumbnailOnly = 0x08,
InCacheOnly = 0x10,
}
public static class WindowsThumbnailProviderWpf
{
private const string IShellItem2Guid = "7E9FB0D3-919F-4307-AB2E-9B1860310C93";
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern int SHCreateItemFromParsingName(
[MarshalAs(UnmanagedType.LPWStr)] string path,
// The following parameter is not used - binding context.
IntPtr pbc,
ref Guid riid,
[MarshalAs(UnmanagedType.Interface)] out IShellItem shellItem);
[DllImport("gdi32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool DeleteObject(IntPtr hObject);
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")]
internal interface IShellItem
{
void BindToHandler(IntPtr pbc,
[MarshalAs(UnmanagedType.LPStruct)]Guid bhid,
[MarshalAs(UnmanagedType.LPStruct)]Guid riid,
out IntPtr ppv);
void GetParent(out IShellItem ppsi);
void GetDisplayName(SIGDN sigdnName, out IntPtr ppszName);
void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs);
void Compare(IShellItem psi, uint hint, out int piOrder);
};
internal enum SIGDN : uint
{
NORMALDISPLAY = 0,
PARENTRELATIVEPARSING = 0x80018001,
PARENTRELATIVEFORADDRESSBAR = 0x8001c001,
DESKTOPABSOLUTEPARSING = 0x80028000,
PARENTRELATIVEEDITING = 0x80031001,
DESKTOPABSOLUTEEDITING = 0x8004c000,
FILESYSPATH = 0x80058000,
URL = 0x80068000
}
internal enum HResult
{
Ok = 0x0000,
False = 0x0001,
InvalidArguments = unchecked((int)0x80070057),
OutOfMemory = unchecked((int)0x8007000E),
NoInterface = unchecked((int)0x80004002),
Fail = unchecked((int)0x80004005),
ElementNotFound = unchecked((int)0x80070490),
TypeElementNotFound = unchecked((int)0x8002802B),
NoObject = unchecked((int)0x800401E5),
Win32ErrorCanceled = 1223,
Canceled = unchecked((int)0x800704C7),
ResourceInUse = unchecked((int)0x800700AA),
AccessDenied = unchecked((int)0x80030005)
}
[ComImport()]
[Guid("bcc18b79-ba16-442f-80c4-8a59c30c463b")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IShellItemImageFactory
{
[PreserveSig]
HResult GetImage(
[In, MarshalAs(UnmanagedType.Struct)] NativeSize size,
[In] ThumbnailOptions flags,
[Out] out IntPtr phbm);
}
[StructLayout(LayoutKind.Sequential)]
internal struct NativeSize
{
private int width;
private int height;
public int Width { set { this.width = value; } }
public int Height { set { this.height = value; } }
};
[StructLayout(LayoutKind.Sequential)]
public struct RGBQUAD
{
public byte rgbBlue;
public byte rgbGreen;
public byte rgbRed;
public byte rgbReserved;
}
public static BitmapImage GetThumbnail(string fileName, int width, int height, ThumbnailOptions options)
{
IntPtr hBitmap = GetHBitmap(Path.GetFullPath(fileName), width, height, options);
try
{
// return a System.Drawing.Bitmap from the hBitmap
return GetBitmapImageFromHBitmap(hBitmap);
}
finally
{
// delete HBitmap to avoid memory leaks
DeleteObject(hBitmap);
}
}
public static BitmapImage GetBitmapImageFromHBitmap(IntPtr nativeHBitmap)
{
var bmpSource = Imaging.CreateBitmapSourceFromHBitmap(nativeHBitmap,
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
var bmpImage = BitmapSourceToBitmapImage(bmpSource);
return bmpImage;
}
// Conversion code
public static BitmapImage BitmapSourceToBitmapImage(BitmapSource bitmapSource)
{
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
MemoryStream memorystream = new MemoryStream();
BitmapImage tmpImage = new BitmapImage();
encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
encoder.Save(memorystream);
tmpImage.BeginInit();
tmpImage.StreamSource = new MemoryStream(memorystream.ToArray());
tmpImage.EndInit();
memorystream.Close();
return tmpImage;
}
public static BitmapImage CreateAlphaBitmapImage(BitmapImage sourceBitmapImage)
{
var bmp = sourceBitmapImage.Clone();
var pixels = new int[(int)bmp.Width * (int)bmp.Height];
var stride = (bmp.PixelWidth * bmp.Format.BitsPerPixel + 7) / 8;
bmp.CopyPixels(pixels, stride, 0);
var oldColor = pixels[0];
var red = 255;
var green = 255;
var blue = 255;
var alpha = 0;
var color = (alpha << 24) + (red << 16) + (green << 8) + blue;
for (var i = 0; i < (int)bmp.Width * (int)bmp.Height; i++)
{
if (pixels[i] == oldColor)
{
pixels[i] = color;
}
}
//remake the bitmap source with these pixels
var source = BitmapSource.Create(bmp.PixelWidth, bmp.PixelHeight, bmp.DpiX, bmp.DpiY, PixelFormats.Bgra32, bmp.Palette, pixels, stride);
//return sourceBitmapImage;
return BitmapSourceToBitmapImage(source);
}
private static IntPtr GetHBitmap(string fileName, int width, int height, ThumbnailOptions options)
{
IShellItem nativeShellItem;
Guid shellItem2Guid = new Guid(IShellItem2Guid);
int retCode = SHCreateItemFromParsingName(fileName, IntPtr.Zero, ref shellItem2Guid, out nativeShellItem);
if (retCode != 0)
throw Marshal.GetExceptionForHR(retCode);
NativeSize nativeSize = new NativeSize();
nativeSize.Width = width;
nativeSize.Height = height;
IntPtr hBitmap;
HResult hr = ((IShellItemImageFactory)nativeShellItem).GetImage(nativeSize, options, out hBitmap);
Marshal.ReleaseComObject(nativeShellItem);
if (hr == HResult.Ok) return hBitmap;
throw Marshal.GetExceptionForHR((int)hr);
}
}
}
While i understand i could just simply reference System.Drawing and use the already working solution, i'd like to know if it's possible to do the same thing in WPF.
Question - Is there a simple way to loop through each pixel of a BitmapImage (similar to a Bitmap), change a specific pixel combination and create a copy of the BitmapImage with transparency?
You may call CopyPixels on a BitmapSource to get the raw pixel buffer, then modify the buffer as you like, and create a new BitmapSource from the modified buffer.
The method below shows how this could work for a BitmapSource with a 32-bit BGRA format.
private static BitmapSource CreateTransparency(BitmapSource source)
{
if (source.Format != PixelFormats.Bgra32)
{
return source;
}
var bytesPerPixel = (source.Format.BitsPerPixel + 7) / 8;
var stride = bytesPerPixel * source.PixelWidth;
var buffer = new byte[stride * source.PixelHeight];
source.CopyPixels(buffer, stride, 0);
for (int y = 0; y < source.PixelHeight; y++)
{
for (int x = 0; x < source.PixelWidth; x++)
{
var i = stride * y + bytesPerPixel * x;
var b = buffer[i];
var g = buffer[i + 1];
var r = buffer[i + 2];
var a = buffer[i + 3];
if (...)
{
buffer[i + 3] = 0d; // set transparent
}
}
}
return BitmapSource.Create(
source.PixelWidth, source.PixelHeight,
source.DpiX, source.DpiY,
source.Format, null, buffer, stride);
}
I create image with LockBits from array in cycle and scale to PictureBox.Width * n and Height:
using (var bmp = new Bitmap(len, _height, PixelFormat.Format24bppRgb))
{
var data = bmp.LockBits(new Rectangle(0, 0, len, _height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
var bytes = data.Stride * data.Height;
var rgb = new byte[bytes];
var ptr = data.Scan0;
Marshal.Copy(data.Scan0, rgb, 0, bytes);
// …fill array „rgb“
Marshal.Copy(rgb, 0, ptr, bytes);
bmp.UnlockBits(data);
g = _pictureBox.CreateGraphics();
g.InterpolationMode = InterpolationMode.Default;
g.DrawImage(bmp, _pictureBox.Width - len * _scaleWidth, 0, len * _scaleWidth, _pictureBox.Height);
}
In the next iterateration:
Graphics g;
using (var bmp = new Bitmap(_pictureBox.Image))
{
g = _pictureBox.CreateGraphics();
g.InterpolationMode = InterpolationMode.Default;
g.DrawImage(_pictureBox.Image, new RectangleF(0, 0, _pictureBox.Width - len * _scaleWidth, _pictureBox.Height), new RectangleF(len * _scaleWidth, 0, _pictureBox.Width * _scaleWidth - len, _height), GraphicsUnit.Pixel);
}
g.Dispose();
In short: I cut and copy part of the image that would shift the picture, but do not get anything. Is it possible because of the scale in the previous step?
Maybe I'm wrong. Advise the algorithm for shifting and adding a new Bitmap into an end.
I advice you to use Control.CreateGraphics() method at Form instance, and write on a Form directly, because PaintBox control is quite slow.
Try using my helper function, this will allow you to paste a portion of your bitmap using StretchBlt (stretching) or BitBlt (no stretching) using Win32 Interop:
Sample usage:
Graphics graphics = ...;
graphics.GdiDrawImage
(
image,
new Rectangle(
(int)rectangle.Left,
(int)rectangle.Top,
(int)rectangle.Width,
(int)rectangle.Height
),
0, 0, image.Width, image.Height
);
Source code:
public static class GraphicsHelper
{
public static void GdiDrawImage(this Graphics graphics, Bitmap image, Rectangle rectangleDst, int nXSrc, int nYSrc, int nWidth, int nHeight)
{
IntPtr hdc = graphics.GetHdc();
IntPtr memdc = GdiInterop.CreateCompatibleDC(hdc);
IntPtr bmp = image.GetHbitmap();
GdiInterop.SelectObject(memdc, bmp);
GdiInterop.SetStretchBltMode(hdc, 0x04);
GdiInterop.StretchBlt(hdc, rectangleDst.Left, rectangleDst.Top, rectangleDst.Width, rectangleDst.Height, memdc, nXSrc, nYSrc, nWidth, nHeight, GdiInterop.TernaryRasterOperations.SRCCOPY);
//GdiInterop.BitBlt(..) put it here, if you did not mention stretching the source image
GdiInterop.DeleteObject(bmp);
GdiInterop.DeleteDC(memdc);
graphics.ReleaseHdc(hdc);
}
}
public class GdiInterop
{
/// <summary>
/// Enumeration for the raster operations used in BitBlt.
/// In C++ these are actually #define. But to use these
/// constants with C#, a new enumeration _type is defined.
/// </summary>
public enum TernaryRasterOperations
{
SRCCOPY = 0x00CC0020, // dest = source
SRCPAINT = 0x00EE0086, // dest = source OR dest
SRCAND = 0x008800C6, // dest = source AND dest
SRCINVERT = 0x00660046, // dest = source XOR dest
SRCERASE = 0x00440328, // dest = source AND (NOT dest)
NOTSRCCOPY = 0x00330008, // dest = (NOT source)
NOTSRCERASE = 0x001100A6, // dest = (NOT src) AND (NOT dest)
MERGECOPY = 0x00C000CA, // dest = (source AND pattern)
MERGEPAINT = 0x00BB0226, // dest = (NOT source) OR dest
PATCOPY = 0x00F00021, // dest = pattern
PATPAINT = 0x00FB0A09, // dest = DPSnoo
PATINVERT = 0x005A0049, // dest = pattern XOR dest
DSTINVERT = 0x00550009, // dest = (NOT dest)
BLACKNESS = 0x00000042, // dest = BLACK
WHITENESS = 0x00FF0062, // dest = WHITE
};
/// <summary>
/// Enumeration to be used for those Win32 function
/// that return BOOL
/// </summary>
public enum Bool
{
False = 0,
True
};
/// <summary>
/// Sets the background color.
/// </summary>
/// <param name="hdc">The HDC.</param>
/// <param name="crColor">Color of the cr.</param>
/// <returns></returns>
[DllImport("gdi32.dll")]
public static extern int SetBkColor(IntPtr hdc, int crColor);
/// <summary>
/// CreateCompatibleDC
/// </summary>
[DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
/// <summary>
/// DeleteDC
/// </summary>
[DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern Bool DeleteDC(IntPtr hdc);
/// <summary>
/// SelectObject
/// </summary>
[DllImport("gdi32.dll", ExactSpelling = true)]
public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
/// <summary>
/// DeleteObject
/// </summary>
[DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern Bool DeleteObject(IntPtr hObject);
/// <summary>
/// CreateCompatibleBitmap
/// </summary>
[DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern IntPtr CreateCompatibleBitmap(IntPtr hObject, int width, int height);
/// <summary>
/// BitBlt
/// </summary>
[DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern Bool BitBlt(IntPtr hObject, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hObjSource, int nXSrc, int nYSrc, TernaryRasterOperations dwRop);
/// <summary>
/// StretchBlt
/// </summary>
[DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern Bool StretchBlt(IntPtr hObject, int nXOriginDest, int nYOriginDest, int nWidthDest, int nHeightDest, IntPtr hObjSource, int nXOriginSrc, int nYOriginSrc, int nWidthSrc, int nHeightSrc, TernaryRasterOperations dwRop);
/// <summary>
/// SetStretchBltMode
/// </summary>
[DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern Bool SetStretchBltMode(IntPtr hObject, int nStretchMode);
}
Notice: This is no full answer to the question, but I want to provide some useful information to the existing answer.
In contrast to the answer of Artur Mustafin, I suggest not to use the Windows GDI directly, but to use the .net methods of the Graphics class. In my tests they have performed a lot better than the GDI functions.
The GDI code is taken from the answer of Artur Mustafin.
The test was displaying an image of 1Mpix (1k x 1k) Bitmap or 100Mpix (10k x 10k) Bitmap in 1k x 1k Picturebox by drawing in the Paint event.
The tests were done in Visual Studio 2015.3 on Windows 7 Pro x64 SP1, Debug mode, on an Intel Core i7-3630QM CPU # 2.4 GHz, 16 GB RAM.
Results:
1000x Graphics.DrawImage() unscaled of 100M Bitmap: 36.8s (x86), 24.2s (x64).
1000x Graphics.DrawImage() unscaled of 1M Bitmap: 5.2s (x86), 3.8s (x64).
100x Graphics.DrawImage() scaled of 100M Bitmap: 62.8s (x86), 39.0s (x64).
1000x Graphics.DrawImage() scaled of 1M Bitmap: 5.2s (x86), 3.8s (x64).
100x GdiDrawImage() StretchBlockTransfer of 100M Bitmap: OutOfMem#x86, 88.5s (x64).
1000x GdiDrawImage() StretchBlockTransfer of 1M Bitmap: 12.9s (x86), 11.5s (x64).
100x GdiDrawImage() BitBlockTransfer of 100M Bitmap: OutOfMem#x86, 49.7s (x64).
1000x GdiDrawImage() BitBlockTransfer of 1M Bitmap: 7.2s (x86), 5.8s (x64).
Test code:
public partial class FormPictureboxPaint : Form
{
private Bitmap m_oBitmap;
public FormPictureboxPaint ()
{
InitializeComponent ();
string sFile = Application.StartupPath + #"\..\..\..\bitmap.png"; // The bitmap file contains an image with 10k x 10k pixels.
m_oBitmap = new Bitmap (sFile);
if (false) // CHANGE TO TRUE IF TESTING WITH 1k x 1k BITMAPS
{
var oBitmap = new Bitmap (m_oBitmap, new Size (1000, 1000));
m_oBitmap.Dispose ();
m_oBitmap = null;
GC.Collect ();
GC.WaitForFullGCComplete ();
GC.WaitForPendingFinalizers ();
m_oBitmap = oBitmap;
}
}
private void pictureBox1_Paint (object sender, PaintEventArgs e)
{
var oGraphics = e.Graphics;
DateTime dtNow = DateTime.Now;
// UNCOMMENT THE FOLLOWING LINES FOR TESTS WITH DrawImage
// COMMENT THE FOLLOWING LINES FOR TESTS WITH GDI
//for (int ixCnt = 0; ixCnt < 1000; ixCnt++)
// PictureboxPaint01 (oGraphics);
// COMMENT THE FOLLOWING LINES FOR TESTS WITH DrawImage
// UNCOMMENT THE FOLLOWING LINES FOR TESTS WITH GDI
for (int ixCnt = 0; ixCnt < 100; ixCnt++)
PictureboxPaint02 (oGraphics);
TimeSpan ts = (DateTime.Now - dtNow);
}
private void PictureboxPaint01 (Graphics i_oGraphics)
{
//_oGraphics.DrawImage (m_oBitmap, new Point ());
i_oGraphics.DrawImage (m_oBitmap, new Rectangle (0, 0, 1000, 1000));
}
private void PictureboxPaint02 (Graphics i_oGraphics)
{
// from https://stackoverflow.com/a/7481071
i_oGraphics.GdiDrawImage
(
m_oBitmap,
new Rectangle (
(int)pictureBox1.Left,
(int)pictureBox1.Top,
(int)pictureBox1.Width,
(int)pictureBox1.Height
),
0, 0, m_oBitmap.Width, m_oBitmap.Height
);
}
}
public static class GraphicsHelper
{
public static void GdiDrawImage (this Graphics graphics, Bitmap image, Rectangle rectangleDst, int nXSrc, int nYSrc, int nWidth, int nHeight)
{
IntPtr hdc = graphics.GetHdc ();
IntPtr memdc = GdiInterop.CreateCompatibleDC (hdc);
IntPtr bmp = image.GetHbitmap ();
GdiInterop.SelectObject (memdc, bmp);
GdiInterop.SetStretchBltMode (hdc, 0x04);
GdiInterop.StretchBlt (hdc, rectangleDst.Left, rectangleDst.Top, rectangleDst.Width, rectangleDst.Height, memdc, nXSrc, nYSrc, nWidth, nHeight, GdiInterop.TernaryRasterOperations.SRCCOPY);
//GdiInterop.BitBlt (hdc, rectangleDst.Left, rectangleDst.Top, rectangleDst.Width, rectangleDst.Height, memdc, nXSrc, nYSrc, GdiInterop.TernaryRasterOperations.SRCCOPY); //put it here, if you did not mention stretching the source image
GdiInterop.DeleteObject (bmp);
GdiInterop.DeleteDC (memdc);
graphics.ReleaseHdc (hdc);
}
}
public class GdiInterop
{
public enum TernaryRasterOperations
{
SRCCOPY = 0x00CC0020, // dest = source
};
public enum Bool
{
False = 0,
True
};
[DllImport ("gdi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern IntPtr CreateCompatibleDC (IntPtr hDC);
[DllImport ("gdi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern Bool DeleteDC (IntPtr hdc);
[DllImport ("gdi32.dll", ExactSpelling = true)]
public static extern IntPtr SelectObject (IntPtr hDC, IntPtr hObject);
[DllImport ("gdi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern Bool DeleteObject (IntPtr hObject);
[DllImport ("gdi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern Bool BitBlt (IntPtr hObject, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hObjSource, int nXSrc, int nYSrc, TernaryRasterOperations dwRop);
[DllImport ("gdi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern Bool StretchBlt (IntPtr hObject, int nXOriginDest, int nYOriginDest, int nWidthDest, int nHeightDest, IntPtr hObjSource, int nXOriginSrc, int nYOriginSrc, int nWidthSrc, int nHeightSrc, TernaryRasterOperations dwRop);
[DllImport ("gdi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern Bool SetStretchBltMode (IntPtr hObject, int nStretchMode);
}
Okay, I am looking for a function or something that will read the color of a certain pixel on my monitor, and when that color is detected, another function will be enabled. I figure using RGB. All help appreciated. Thank You.
This is the most efficient: It grabs a pixel at the location of the cursor, and doesn't rely on only having one monitor.
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Diagnostics;
namespace FormTest
{
public partial class Form1 : Form
{
[DllImport("user32.dll")]
static extern bool GetCursorPos(ref Point lpPoint);
[DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
public static extern int BitBlt(IntPtr hDC, int x, int y, int nWidth, int nHeight, IntPtr hSrcDC, int xSrc, int ySrc, int dwRop);
public Form1()
{
InitializeComponent();
}
private void MouseMoveTimer_Tick(object sender, EventArgs e)
{
Point cursor = new Point();
GetCursorPos(ref cursor);
var c = GetColorAt(cursor);
this.BackColor = c;
if (c.R == c.G && c.G < 64 && c.B > 128)
{
MessageBox.Show("Blue");
}
}
Bitmap screenPixel = new Bitmap(1, 1, PixelFormat.Format32bppArgb);
public Color GetColorAt(Point location)
{
using (Graphics gdest = Graphics.FromImage(screenPixel))
{
using (Graphics gsrc = Graphics.FromHwnd(IntPtr.Zero))
{
IntPtr hSrcDC = gsrc.GetHdc();
IntPtr hDC = gdest.GetHdc();
int retval = BitBlt(hDC, 0, 0, 1, 1, hSrcDC, location.X, location.Y, (int)CopyPixelOperation.SourceCopy);
gdest.ReleaseHdc();
gsrc.ReleaseHdc();
}
}
return screenPixel.GetPixel(0, 0);
}
}
}
Now, obviously, you don't have to use the cursor's current location, but this is the general idea.
EDIT:
Given the above GetColorAt function you can poll a certain pixel on the screen in a safe, performance friendly way like this:
private void PollPixel(Point location, Color color)
{
while(true)
{
var c = GetColorAt(location);
if (c.R == color.R && c.G == color.G && c.B == color.B)
{
DoAction();
return;
}
// By calling Thread.Sleep() without a parameter, we are signaling to the
// operating system that we only want to sleep long enough for other
// applications. As soon as the other apps yield their CPU time, we will
// regain control.
Thread.Sleep()
}
}
You can wrap that in a Thread if you want, or execute it from a Console application. "Whatever suits your fancy," I guess.
Most answers here use the very same source of that pixel (desktop dc).
The key function is GetPixel.
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetWindowDC(IntPtr window);
[DllImport("gdi32.dll", SetLastError = true)]
public static extern uint GetPixel(IntPtr dc, int x, int y);
[DllImport("user32.dll", SetLastError = true)]
public static extern int ReleaseDC(IntPtr window, IntPtr dc);
public static Color GetColorAt(int x, int y)
{
IntPtr desk = GetDesktopWindow();
IntPtr dc = GetWindowDC(desk);
int a = (int) GetPixel(dc, x, y);
ReleaseDC(desk, dc);
return Color.FromArgb(255, (a >> 0) & 0xff, (a >> 8) & 0xff, (a >> 16) & 0xff);
}
I think this is the cleanest and quickest way.
Note:
If you have modified the default text size among the Display Settings on Windows to increase readability on a high resolution display, the coordinate parameters of GetPixel() need to be adjusted the same way. For example, if the cursor location is (x,y) with 150% of text size on Windows 7, you need to call GetPixel(x*1.5, y*1.5) to get the color of the pixel under the cursor.
This function is shorter and can achieve the same result using System.Drawing, without Pinvoke.
Color GetColorAt(int x, int y)
{
Bitmap bmp = new Bitmap(1, 1);
Rectangle bounds = new Rectangle(x, y, 1, 1);
using (Graphics g = Graphics.FromImage(bmp))
g.CopyFromScreen(bounds.Location, Point.Empty, bounds.Size);
return bmp.GetPixel(0, 0);
}
Please check this two different functions I have used in one of my previous projects :
1) This function takes snapshot of Desktop
private void CaptureScreenAndSave(string strSavePath)
{
//SetTitle("Capturing Screen...");
Bitmap bmpScreenshot;
Graphics gfxScreenshot;
bmpScreenshot = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height,System.Drawing.Imaging.PixelFormat.Format32bppArgb);
gfxScreenshot = Graphics.FromImage(bmpScreenshot);
gfxScreenshot.CopyFromScreen(Screen.PrimaryScreen.Bounds.X, Screen.PrimaryScreen.Bounds.Y, 0, 0, Screen.PrimaryScreen.Bounds.Size, CopyPixelOperation.SourceCopy);
MemoryStream msIn = new MemoryStream();
bmpScreenshot.Save(msIn, System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders()[0], null);
msIn.Close();
byte[] buf = msIn.ToArray();
MemoryStream msOut = new MemoryStream();
msOut.Write(buf, 0, buf.Length);
msOut.Position = 0;
Bitmap bmpOut = new Bitmap(msOut);
try
{
bmpOut.Save(strSavePath, System.Drawing.Imaging.ImageFormat.Bmp);
//SetTitle("Capturing Screen Image Saved...");
}
catch (Exception exp)
{
}
finally
{
msOut.Close();
}
}
2) This function takes an image in input and calculates RGB average of pixel range given.
double GetRGBAverageForPixelRange( int istartRange, int iEndRange, Bitmap oBitmap )
{
double dRetnVal = 0 ;
Color oTempColor ;
int i, j ;
for( int iCounter = istartRange ; iCounter < iEndRange ; iCounter++ )
{
i = (iCounter % (oBitmap.Width));
j = ( iCounter / ( oBitmap.Width ) ) ;
if (i >= 0 && j >= 0 && i < oBitmap.Width && j < oBitmap.Height )
{
oTempColor = oBitmap.GetPixel(i, j);
dRetnVal = dRetnVal + oTempColor.ToArgb();
}
}
return dRetnVal ;
}
This two functions together might solve your problem. Happy Coding :)
EDIT : Please note that GetPixel is very slow function. I will think twice befor using it.
As far as I know the easiest way to do this is to:
take a screenshot
look at the bitmap and get the pixel color
Edit
There is probably no way to "wait" until the pixel changes to a certain color. Your program will probably have to just loop and check it every so often until it sees the color.
For example:
while(!IsPixelColor(x, y, color))
{
//probably best to add a sleep here so your program doesn't use too much CPU
}
DoAction();
EDIT 2
Here is some sample code you can modify. This code just changes the color of a label based on the current color in a given pixel. This code avoids the handle leak mentioned.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Runtime.InteropServices;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
[DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
public static extern int BitBlt(IntPtr hDC, int x, int y, int nWidth, int nHeight, IntPtr hSrcDC, int xSrc, int ySrc, int dwRop);
Thread t;
int x, y;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
x = 20;
y = 50;
t = new Thread(update);
t.Start();
}
private void update()
{
Bitmap screenCopy = new Bitmap(1, 1);
using (Graphics gdest = Graphics.FromImage(screenCopy))
{
while (true)
{
//g.CopyFromScreen(new Point(0, 0), new Point(0, 0), new Size(256, 256));
using (Graphics gsrc = Graphics.FromHwnd(IntPtr.Zero))
{
IntPtr hSrcDC = gsrc.GetHdc();
IntPtr hDC = gdest.GetHdc();
int retval = BitBlt(hDC, 0, 0, 1, 1, hSrcDC, x, y, (int)CopyPixelOperation.SourceCopy);
gdest.ReleaseHdc();
gsrc.ReleaseHdc();
}
Color c = Color.FromArgb(screenCopy.GetPixel(0, 0).ToArgb());
label1.ForeColor = c;
}
}
}
}
}
This line uses About 10 ms.
int retval = BitBlt(hDC, 0, 0, 1, 1, hSrcDC, location.X, location.Y, (int)CopyPixelOperation.SourceCopy);