יום שבת, 21 ביוני 2014

Seven Segment Display Guide




במהלך הסדרה עברתי על המון רכיבים שניתן לחבר ל Arduino ושכחתי מתצוגת ה 7 Segment הותיקה שנתקלתי בה כלכך הרבה פעמים בעבר שזה יהיה עוול לא לכתוב עליה משהו בקצרה, היא מאוד פרימיטיבית אבל שימושית בפרוייקטים שצריכים תצוגה מינימלית, היא זולה, פשוטה ואמינה, ניתן לרכוש אותה ב DX בשקלים בודדים.

דרישות

  • (Arduino (any kind will do the job
  • 7 Segments 
  • Ohm Resistor 330
  • Breadboard
  • Wires


צורת העבודה מאוד בזבזנית ונאלץ להיפרד מ 8 חיבורים דיגיטליים ב Arduino על מנת שנוכל להשתמש בה כהלכה, כל רגל מייצגת Segment בודד וניתן לשלוט עליו בנפרד, על מנת לצמצם את כמות הרגליים מומלץ להשתמש ב Shift Register.

הרגל ה 8 היא עבור הנקודה שלא חלק מה Segments

קוד

//array of numbers, each number contain
//array of the 7 segment pins status, 
//0 for low , 1 for high
byte numbers[12][8] ={
 {1,0,0,0,0,1,0,0},//0
  {1,0,1,1,1,1,1,0},//1
 {1,1,0,0,1,0,0,0},//2
 {1,0,0,1,1,0,0,0},//3
 {1,0,1,1,0,0,1,0},//4
 {1,0,0,1,0,0,0,1},//5
 {1,0,0,0,0,0,0,1},//6
 {1,0,1,1,1,1,0,0},//7
 {1,0,0,0,0,0,0,0},//8
 {1,0,0,1,0,0,0,0}, //9
 {0,1,1,1,1,1,1,1} ,//.
 {1,1,1,1,1,1,1,1} //clear
 } ;
                                                       
//set pins as outputs
void setup() {              
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);

  Serial.begin(9600);
}

//change the segments by number
void draw(byte num)
{
  //starting pin
  byte pin = 2;
  for(byte seg = 0; seg < 8; ++seg)
  {
    //write the segment status
     digitalWrite(pin,numbers[num][seg]);
     //go to the next pin
     ++pin;
  }
}

void loop() {

  int counter = 0;

    while(counter < 11)
  {
       delay(500);
       draw(counter);
       counter = counter + 1;
  }

  draw(10);
  delay(500);
  draw(11);
  delay(100);
  draw(10);
  delay(500);

  while(counter >= -1)
  {
     delay(500);
     draw(counter);
     counter = counter - 1;
  }

  draw(10);
  delay(500);
  draw(11);
  delay(100);
  draw(10);
  delay(500);

}


קיים מערך שמכיל בתוכו 10 מערכים נוספים שבכל אחד מהם יש ערך עבור כל Segment, לצורך הדוגמה המערך {1,0,0,0,0,0,0,0} מייצג את הספירה 8 כי כל ה Segments דולקים למעט ה Segment הנוסף של הנקודה בצד.  


מבנה סופי
מספר הולך, מספר בא.

סרטון הדגמה





סיכום

היו זמנים שהיא היתה נחשבת לפורצת דרך אבל עם הזמן השימוש בה ירד לטובת תצוגות הרבה יותר מתוחכמות (וטוב שכך!) אבל עדיין ניתן למצוא אותה במוצרים שונים ולא נראה שהיא הולכת להעלם בקרוב.

תצוגה בגרוש.






יום שני, 2 ביוני 2014

Remote Control Application - ScreenShot




עד עכשיו נגעתי בסדרה בכלי שליטה מתוכנתים בצד הלקוח, השלב הבא הוא לכידת התמונה בצד השרת באמצעות הספריה הקלאסית GDI, זה לא המנגנון היעיל ביותר אבל מספיק מתאים בשביל המשימה, לכידת המסך צריכה לעמוד בזמנים יחסית טובים על מנת לשמור על חווית משתמש, לא פחות חשוב שניהול הזיכרון צריך להיות מתוחזק כמו שצריך למרות שמדובר ב .Net על מנת למנוע זליגת זיכרון.

בנוסף לקובץ user32.dll שהשתמשנו בעבר נעבוד עם קובץ נוסף GDI.dll שמאפשר לנו לגשת למשאבים הגרפיים במחשב ע"י מספר פונקציות תחילה מקבלים את המצביע עבור הרכיב הפיזי בעזרת הפונקציה CreateDC לאחר מכן יוצרים Buffer משותף עם הפונקציה CreateCompatibleDC, השלב הבא להצהיר על Pointer ל Bitmap של Win32Api עם הפונקציה CreateCompatibleBitmap , מחברים בין ה Bitmap ל Buffer וקוראים לפונקציה BitBlt שאוספת את הפריים מהרכיב הפיזי ומעתיקה אותו ל Buffer ומשם ממשיכים הלאה.

captureControl.cs

       //global stream
        MemoryStream stream;

        //isRunning flag
        public bool isRunning = false;

        int width = 0;
        int height = 0;

        //capture signature
        [DllImport("GDI32.dll")]
        public static extern bool BitBlt(
            int hdcDest, int nXDest, int nYDest,
               int nWidth, int nHeight, int hdcSrc,
            int nXSrc, int nYSrc, int dwRop);

        //create compatible bitmap for device context
        [DllImport("GDI32.dll")]
        public static extern int CreateCompatibleBitmap
            (int hdc, int nWidth, int nHeight);

        //create compatible device context for the device
        [DllImport("GDI32.dll")]
        public static extern int CreateCompatibleDC(int hdc);

        //delete driver context
        [DllImport("GDI32.dll")]
        public static extern bool DeleteDC(int hdc);

        //delete native bitmap type
        [DllImport("GDI32.dll")]
        public static extern bool DeleteObject(int hObject);

        //create device context
        [DllImport("gdi32.dll")]
        static extern int CreateDC(string lpszDriver,
            string lpszDevice,string lpszOutput,
            IntPtr lpInitData);

        //get device information
        [DllImport("GDI32.dll")]
        public static extern int GetDeviceCaps(int hdc, int nIndex);

        //add object to the device context
        [DllImport("GDI32.dll")]
        public static extern int SelectObject(int hdc, int hgdiobj);

        //cursor object win32 api structure
        [StructLayout(LayoutKind.Sequential)]
        private struct CURSORINFO
        {
            public Int32 cbSize;
            public Int32 flags;
            public IntPtr hCursor;
            public POINTAPI ptScreenPos;
        }

        //point type win32 structure
        [StructLayout(LayoutKind.Sequential)]
        private struct POINTAPI
        {
            public int x;
            public int y;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct ICONINFO
        {
            public bool fIcon;
            public Int32 xHotspot;
            public Int32 yHotspot;
            public IntPtr hbmMask;
            public IntPtr hbmColor;
        }

        ////for more information visit
        ////http://msdn.microsoft.com/en-us/library/windows/desktop/dd145130(v=vs.85).aspx
        public enum TernaryRasterOperations : uint
        {
            /// <summary>dest = source</summary>
            SRCCOPY = 0x00CC0020,
            /// <summary>dest = source OR dest</summary>
            SRCPAINT = 0x00EE0086,
            /// <summary>dest = source AND dest</summary>
            SRCAND = 0x008800C6,
            /// <summary>dest = source XOR dest</summary>
            SRCINVERT = 0x00660046,
            /// <summary>dest = source AND (NOT dest)</summary>
            SRCERASE = 0x00440328,
            /// <summary>dest = (NOT source)</summary>
            NOTSRCCOPY = 0x00330008,
            /// <summary>dest = (NOT src) AND (NOT dest)</summary>
            NOTSRCERASE = 0x001100A6,
            /// <summary>dest = (source AND pattern)</summary>
            MERGECOPY = 0x00C000CA,
            /// <summary>dest = (NOT source) OR dest</summary>
            MERGEPAINT = 0x00BB0226,
            /// <summary>dest = pattern</summary>
            PATCOPY = 0x00F00021,
            /// <summary>dest = DPSnoo</summary>
            PATPAINT = 0x00FB0A09,
            /// <summary>dest = pattern XOR dest</summary>
            PATINVERT = 0x005A0049,
            /// <summary>dest = (NOT dest)</summary>
            DSTINVERT = 0x00550009,
            /// <summary>dest = BLACK</summary>
            BLACKNESS = 0x00000042,
            /// <summary>dest = WHITE</summary>
            WHITENESS = 0x00FF0062
        }


        //for more info visit
        //http://msdn.microsoft.com/en-us/library/windows/desktop/ms648381(v=vs.85).aspx
        private const Int32 CURSOR_SHOWING = 0x1;
        private const Int32 CURSOR_SUPPRESSED = 0x2;

        //getting cursor info signature
        [DllImport("user32.dll")]
        private static extern bool GetCursorInfo(out CURSORINFO pci);

        //getting cursor info signature
        [DllImport("user32.dll", EntryPoint = "GetIconInfo")]
        public static extern bool GetIconInfo(IntPtr hIcon, out ICONINFO piconinfo);

        //getting icon buffer signature
        [DllImport("user32.dll", EntryPoint = "CopyIcon")]
        public static extern IntPtr CopyIcon(IntPtr hIcon);

        //return handle to the desktop window
        [DllImport("user32.dll", EntryPoint = "GetDesktopWindow")]
        public static extern IntPtr GetDesktopWindow();

        //getting device context
        [DllImport("user32.dll", EntryPoint = "GetDC")]
        public static extern IntPtr GetDC(IntPtr ptr);


        /// <summary>
        /// initialize settings 
        /// </summary>
        /// <param name="captureWidth">the width to capture</param>
        /// <param name="captureHeight">the height to cature</param>
        public captureControl(int captureWidth, int captureHeight)
        {
            width = captureWidth;
            height = captureHeight;
        }

        /// <summary>
        /// capture screen shot
        /// </summary>
        /// <returns>return stream</returns>
        public MemoryStream makeScreenShot()
        {
            //process flag to true
            isRunning = true;

            //create stream for return
            stream = new MemoryStream();

            //X draw position - cursor
            int x = 0;
            //Y draw position - cursor
            int y = 0;

            //create device context for Display device
            int hdcSrc = CreateDC("Display", null, null, IntPtr.Zero);

            //make it compatible.
            int hdcDest = CreateCompatibleDC(hdcSrc);
            int hBitmap = CreateCompatibleBitmap(hdcSrc,
                Screen.PrimaryScreen.Bounds.Width,
                Screen.PrimaryScreen.Bounds.Height);

            //add object to device context
            SelectObject(hdcDest, hBitmap);

            //make bit transfer from device
            BitBlt(hdcDest, 0, 0, Screen.PrimaryScreen.Bounds.Width,
                Screen.PrimaryScreen.Bounds.Height, hdcSrc, 0, 0,
                             (int)TernaryRasterOperations.SRCCOPY);

            //get image from native bitmap pointer
            Image imf = Image.FromHbitmap(new IntPtr(hBitmap));

            //start graphics from image
            Graphics g = Graphics.FromImage(imf);

            //get cursor bitmap and position
            Bitmap f = getCursorIcon(out x, out y);

            //draw the cursor the image by position
            if (f != null)
                g.DrawImage(f, x, y);

            //encoding parameters
            EncoderParameters encoderParameters = new EncoderParameters(1);
            encoderParameters.Param[0] =
                new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 70L);

            //save image as stream
            imf.Save(stream, GetEncoder(ImageFormat.Jpeg), encoderParameters);

            //remove device context
            DeleteDC(hdcSrc);
            //remove device context
            DeleteDC(hdcDest);
            //remove native bitmap
            DeleteObject(hBitmap);

            //resize the image
            stream = resizeImage(Image.FromStream(stream), new Size(width, height));

            //process flag to false
            isRunning = false;

            return stream;
        }

        /// <summary>
        /// getting the cursor image
        /// </summary>
        /// <param name="x">cursor x position</param>
        /// <param name="y">cursor y poistion</param>
        /// <returns>the cursor bitmap</returns>
        private Bitmap getCursorIcon(out int x, out int y)
        {
            Rectangle bounds = Screen.PrimaryScreen.Bounds;
            CURSORINFO pci;
            pci.cbSize = Marshal.SizeOf(typeof(CURSORINFO));

            x = 0;
            y = 0;


            if (GetCursorInfo(out pci))
            {
                x = pci.ptScreenPos.x - bounds.X;
                y = pci.ptScreenPos.y - bounds.Y;

                IntPtr hicon = CopyIcon(pci.hCursor);

                ICONINFO iconInfo;
                GetIconInfo(hicon, out iconInfo);

                Bitmap maskCursor = Bitmap.FromHbitmap(iconInfo.hbmMask);

                // if the size of the icon is double
                //so is in inverted mode
                if (maskCursor.Height == maskCursor.Width * 2)
                {
                    x = pci.ptScreenPos.x - bounds.X;
                    y = pci.ptScreenPos.y - bounds.Y - 8;

                    //create new bitmap for cursor
                    Bitmap resultBitmap = new Bitmap(maskCursor.Width, maskCursor.Height);

                    //get the desktop
                    Graphics desktopGraphics = Graphics.FromHwnd(GetDesktopWindow());
                    //get it's device context
                    IntPtr desktopHdc = desktopGraphics.GetHdc();

                    //make it compatible
                    int maskHdc = CreateCompatibleDC(desktopHdc.ToInt32());

                    //set object between context and the cursor
                    int oldPtr = SelectObject(maskHdc, (int)maskCursor.GetHbitmap());

                    //start graphics from image
                    Graphics resultGraphics = Graphics.FromImage(resultBitmap);

                    //get device context
                    IntPtr resultHdc = resultGraphics.GetHdc();

                    //combines image bit depends the Ternary Raster Operations
                    //first (0 - 32) pixels draw normaly, (32 - 64) as inverted
                    BitBlt(resultHdc.ToInt32(), 0, 0, 32, 32, maskHdc,
                        0, 32, (int)TernaryRasterOperations.SRCCOPY);
               
                    BitBlt(resultHdc.ToInt32(), 0, 0, 32, 32, maskHdc,
                        0, 0, (int)TernaryRasterOperations.SRCINVERT);

                    //release device context
                    resultGraphics.ReleaseHdc(resultHdc);
                    //dispose grahics
                    resultGraphics.Dispose();

                    //make white pixels transparent
                    resultBitmap.MakeTransparent(Color.White);

                    return resultBitmap;

                }
                else
                {
                    //get bitmap from pointer
                    Bitmap colorCursor = Bitmap.FromHbitmap(iconInfo.hbmColor);

                    //make black pixels transparent
                    colorCursor.MakeTransparent(Color.Black);

                    return colorCursor;
                }

            }

            return null;
        }

       /// <summary>
       /// resize the capture image
       /// </summary>
       /// <param name="imgToResize">instance of the image</param>
       /// <param name="size">the new size</param>
       /// <returns>new image stream</returns>
        private MemoryStream resizeImage(Image imgToResize, Size size)
        {
            MemoryStream resizeStream = new MemoryStream();
            int sourceWidth = imgToResize.Width;
            int sourceHeight = imgToResize.Height;

            float nPercent = 0;
            float nPercentW = 0;
            float nPercentH = 0;

            nPercentW = ((float)size.Width / (float)sourceWidth);
            nPercentH = ((float)size.Height / (float)sourceHeight);

            if (nPercentH < nPercentW)
                nPercent = nPercentH;
            else
                nPercent = nPercentW;

            int destWidth = (int)(sourceWidth * nPercent);
            int destHeight = (int)(sourceHeight * nPercent);

            resizeStream = new MemoryStream();

            Bitmap b = new Bitmap(destWidth, destHeight);
            Graphics g = Graphics.FromImage((Image)b);
            g.InterpolationMode = InterpolationMode.HighQualityBicubic;

            g.DrawImage(imgToResize, 0, 0, destWidth, destHeight);
            g.Dispose();

            b.Save(resizeStream, ImageFormat.Jpeg);


            return resizeStream;
        }

        /// <summary>
        /// get the jpg codec
        /// </summary>
        /// <param name="format">by Imageformat</param>
        /// <returns></returns>
        private ImageCodecInfo GetEncoder(ImageFormat format)
        {

            ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();

            foreach (ImageCodecInfo codec in codecs)
            {
                if (codec.FormatID == format.Guid)
                {
                    return codec;
                }
            }
            return null;
        }
    }


ברגע הקריאה לפונקציה makeScreenShot מתחילה שרשרת של תהליכים, תחילה לוכדים את התמונה כפי שהסברתי בהתחלה, אבל לכידת המסך לא אוספת את סמן העכבר ולכן יש לקרוא למספר פונקציות נוספות על מנת למצוא אותו ולצייר אותו ידנית אבל גם זה לא פשוט כמו שזה נשמע, למצביעים בעכבר (Cursor) יש 2 מצבים הגודל שלהם בדר"כ הוא  32 פיקסלים אבל לפעמים הוא משתנה ל 64 פיקסלים בגלל הרקע שמתחת, נעזר במספר פונקציות ומבנים מ user32.dll על מנת לטפל במצבים השונים, בסיום התהליך לוקחים את הפריים, מורידים לו את האיכות ,מקטינים ומחזרים אותו להמשך טיפול.

בדומה לשאר המאמרים גם כאן נבנה תוכנית שנועדה לבדיקת התהליך, התוכנית מפעילה את הפונקציה makeScreenShot בעזרת שעון שדוגם (אם אין תהליך דגימה שכבר רץ) כל 10 ms, שומרים את הפריים כ Jpg על מנת להקטין נפחים וטוענים את ה Stream ל PictureBox שמדמה את הלקוח המרוחק.


מסך בתוך מסך בתוך מסך...

captureForm.cs

        captureControl c;
        int frameCounter = 0;

        public captureForm()
        {
            InitializeComponent();
        }

        private void captureForm_Load(object sender, EventArgs e)
        {
            //create new instance for capture control
            c = new captureControl(1200, 600);
            //fit image to picturebox
            pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
        }

        /// <summary>
        /// running every 10ms
        /// </summary>
        private void timer1_Tick(object sender, EventArgs e)
        {
            //getting screenshot as stream
            if (c.isRunning)
                return;

            try
            {
                //watch for checking performance
                Stopwatch watch = new Stopwatch();

                watch.Start();
                MemoryStream st = c.makeScreenShot();
                watch.Stop();

                //loaded to the pictureBox
                pictureBox1.Image = Image.FromStream(st);

                //change title and calculate the frame size
                frameCounter = frameCounter + 1;
                this.Text = "captureScreen Frame Counter "
                    + frameCounter.ToString() + " Frame Size: "
                    + (st.Length / 1024).ToString() + " KB width:"
                    + pictureBox1.Image.Size.Width.ToString() + " px Height: "
                    + pictureBox1.Image.Size.Height.ToString() +
                    " px Capture Time: " + watch.ElapsedMilliseconds + " ms";

                //dispose the stream, avoid memory leak
                st.Dispose();
                //call the garbage collector
                GC.Collect();

            }
            catch (Exception ex)
            {
                c.isRunning = false;
            }
        }
    }
ביצועים

לכידת התמונה יחד עם ציור סמן העכבר לוקחת בסביבות 150 ms ברזולוציה 1920X1080 עם מעבד 7 Icore (יחסית ישן) מה שנותן בין 6 ל 7 פריימים בשניה, זה לא הכי אידיאלי אבל אם מורידים את הרזולוציה משפרים את המהירות בצורה משמעותית, מבדיקה נוספת שביצעתי על מחשב נייד עם Icore 7 (גם הוא יחסית ישן) לכידת תמונה ברזולוציה 1366X768 לוקחת בסביבות 110 ms.

סיכום

לכידת מסך היא הרכיב האחרון בפאזל לפני שמתחילים לחבר את הכל, אבל חשוב לי להוסיף שקיימות עוד דרכים שאיתן ניתן לבצע את המשימה כמו:

  • DirectX - לכידת מסך גם מחוץ ל Desktop לדוגמה במשחקים.
  • Windows Media Encoder - מתאים בעיקר ללכידת מסך כוידאו.
  • Mirror Display Driver - הדרך הטובה ביותר אבל מאוד מסובכת, נמצאת בשימוש ב UltraVnc, לכל מי שרוצה לדעת עוד יש דוגמה טובה ב Wdk גרסת 7.600 אולי עוד אחזור לזה בהמשך.

  • עוד תמונה ועוד תמונה...