/// <summary> /// Clears the button at the given index and moves it to the endIdx-1 position. /// </summary> /// <param name="screenIdx">The index of the button to clear.</param> public void ClearBtn(int screenIdx) { if (screenIdx < startIdx || screenIdx >= endIdx) { throw new IndexOutOfRangeException("Index for this button manager must be in [" + startIdx + ", " + (endIdx - 1) + "], was " + screenIdx); } try { clickLock.WaitOne(); for (int i = screenIdx + 1; i < endIdx; i++) { GetBtn(i).SetIndex(i - 1); } BtnProps btn = btns[screenIdx]; btns.RemoveAt(screenIdx); btns.Insert(endIdx - 1, btn); btn.SetIndex(endIdx - 1); btn.SetImage(null); } finally { clickLock.ReleaseMutex(); } }
private static void CalculateNextDrawCommand(uint colSpan, uint rowSpan, BtnProps btn, int res, int[] line) { Bitmap img = btn.screenImg; for (uint col = 0; col < line.Length; col++) { int x = (int)(col * colSpan); if (colSpan == 1 && rowSpan == 1) { // nothing to average for a 1x1 square of color line[col] = ImageMagic.ColorConvertColorToInt(img.GetPixel(x, (int)btn.rowIdx[res])); } else if (colSpan * rowSpan < 625_000) // arbitrary 10MB limit { // calculate the median color List <Tuple <Color, int> > colorSwatch = new List <Tuple <Color, int> >((int)(colSpan * rowSpan)); colorSwatch.OrderBy(t => t.Item2); for (int i = 0; i < colSpan; i++) { for (int j = 0; j < rowSpan; j++) { int y = (int)(btn.rowIdx[res] * rowSpan); Color color = img.GetPixel(x + i, y + j); int intensity = color.R + color.G + color.B; colorSwatch.Add(new Tuple <Color, int>(color, intensity)); } } Color c = colorSwatch[colorSwatch.Count / 2].Item1; line[col] = ImageMagic.ColorConvertColorToInt(c); } else { // calculate the average color ulong[] uc = new ulong[4]; for (int i = 0; i < colSpan; i++) { for (int j = 0; j < rowSpan; j++) { int y = (int)(btn.rowIdx[res] * rowSpan); Color color = img.GetPixel(x + i, y + j); uc[0] += color.R; uc[1] += color.G; uc[2] += color.B; } } uint pixelCnt = colSpan * rowSpan; uint r = (uint)uc[0] / pixelCnt; uint g = (uint)uc[1] / pixelCnt; uint b = (uint)uc[2] / pixelCnt; line[col] = ImageMagic.ColorConvertRGBTo24(r, g, b); } } }
/// <summary> /// Queues up an image to be drawn to an Arduino screen. /// A low-res image (8x8) will be loaded first and in reverse queue order for speed, /// then a medium-res image (40x32) will be loaded in queue order, /// then finally a full-res image (160x128) will be loaded in queue order. /// /// Queueing up a new image for the same screen will /// restart the process imediately and the old image data /// will be forgotten. /// </summary> /// <param name="screenIdx">The screen to draw to.</param> /// <param name="updateLowRes">True to push the image for the given screen in low resolution to the <see cref="comm"/></param> /// <param name="updateMedRes">True to push the image for the given screen in medium resolution to the <see cref="comm"/></param> /// <param name="updateHighRes">True to push the image for the given screen in high resolution to the <see cref="comm"/></param> public void QueueImage(int screenIdx, bool updateLowRes = true, bool updateMedRes = true, bool updateHighRes = true) { try { queueMutex.WaitOne(); // discard previous updates if (updateLowRes) { lowResUpdates.Remove(screenIdx); } if (updateMedRes) { medResUpdates.Remove(screenIdx); } if (updateHighRes) { highResUpdates.Remove(screenIdx); } // add new update if (updateLowRes) { lowResUpdates.Add(screenIdx); } if (updateMedRes) { medResUpdates.Insert(0, screenIdx); } if (updateHighRes) { highResUpdates.Insert(0, screenIdx); } // reset the rowIdx for the updates, as necessary BtnProps btn = btns[screenIdx]; if (updateLowRes) { btn.rowIdx[0] = 0; } if (updateMedRes) { btn.rowIdx[1] = 0; } if (updateHighRes) { btn.rowIdx[2] = 0; } } finally { queueMutex.ReleaseMutex(); } }
public BtnProps GetBtn(int screenIdx) { if (screenIdx < startIdx || screenIdx >= endIdx) { throw new IndexOutOfRangeException("Index for this button manager must be in [" + startIdx + ", " + (endIdx - 1) + "], was " + screenIdx); } BtnProps btn = btns[screenIdx]; if (btn.idx != screenIdx) { throw new InvalidOperationException("The button at index " + screenIdx + " has an internally registered index of " + screenIdx); } return(btn); }
/// <summary> /// Draws lines from the images waiting in the update stacks/queues. /// Only draws one line of the most low-res image to keep the thread responsive. /// </summary> /// <returns>True when an update succeeds or there is no update to do, false when an update fails.</returns> public bool Update() { uint colSpan = 0; uint rowSpan = 0; int screenIdx = -1; BtnProps btn = null; Bitmap img = null; int res = 0; // low (0), med (1), high (2) try { queueMutex.WaitOne(); // get the quality to draw a line at screenIdx = -1; btn = null; if (lowResUpdates.Count > 0) { colSpan = 20; rowSpan = 16; screenIdx = lowResUpdates[0]; btn = btns[screenIdx]; res = 0; if ((btn.rowIdx[res] + 1) * rowSpan == btn.screenHeight) { lowResUpdates.RemoveAt(0); } if (btn.rowIdx[res] == 0) { Console.WriteLine($"drawing low-rez {screenIdx}"); } } else if (medResUpdates.Count > 0) { colSpan = 4; rowSpan = 4; screenIdx = medResUpdates[medResUpdates.Count - 1]; btn = btns[screenIdx]; res = 1; if ((btn.rowIdx[res] + 1) * rowSpan == btn.screenHeight) { medResUpdates.RemoveAt(medResUpdates.Count - 1); } if (btn.rowIdx[res] == 0) { Console.WriteLine($"drawing med-rez {screenIdx}"); } } else if (highResUpdates.Count > 0) { colSpan = 1; rowSpan = 1; screenIdx = highResUpdates[highResUpdates.Count - 1]; btn = btns[screenIdx]; res = 2; if ((btn.rowIdx[res] + 1) * rowSpan == btn.screenHeight) { highResUpdates.RemoveAt(highResUpdates.Count - 1); } if (btn.rowIdx[res] == 0) { Console.WriteLine($"drawing high-rez {screenIdx}"); } } // for images we can't update, pretend like we did an update if (btn == null || btn.comm == null) { if (btn != null) { btn.rowIdx[res]++; } return(true); } // get the image img = btn.screenImg; // calculate the line to draw int[] line = new int[btn.screenWidth / colSpan]; CalculateNextDrawCommand(colSpan, rowSpan, btn, res, line); // draw the line if (btn.rowIdx[res] == 0) { swFrame.Restart(); } if (!btn.comm.SendImageRow(line, btn.rowIdx[res], rowSpan, btn)) { return(false); } // prepare for next time this function is called btn.rowIdx[res]++; if (btn.rowIdx[res] * rowSpan >= btn.screenHeight) { btn.rowIdx[res] = 0; Console.WriteLine(swFrame.ElapsedMilliseconds); } } finally { queueMutex.ReleaseMutex(); } return(true); }
/// <summary> /// Tries to send an image to the /// </summary> /// <param name="row">The row data to draw.</param> /// <param name="startRow">The starting y-index of the row (top).</param> /// <param name="rowSpan">The number of vertical pixels to draw this same row over (aka inverse of img resolution).</param> /// <returns>True on success, false on failure. If false, then the device is most likely disconnected.</returns> public abstract bool SendImageRow(int[] row, uint startRow, uint rowSpan, BtnProps btn);
public override bool SendImageRow(int[] line, uint startRow, uint rowSpan, BtnProps btn) { Stopwatch timer = new Stopwatch(); timer.Start(); int idx = 0; // check that WIDTH is a multiple of the line length uint colSpan = screenWidth / (uint)line.Length; if (line.Length * colSpan != screenWidth) { throw new ArgumentException($"The display width ({screenWidth}) must be a multiple of the line length ({line.Length})"); } // send packetized data if (startRow == 0) { byte[] restart = new byte[62]; // buffer to be sent idx = BitBashing.UIntToDecimalBytes(ref restart, 0, 2, 0); restart[idx++] = Convert.ToByte(':'); restart[idx++] = Convert.ToByte('R'); // "Reset Draw" command restart[idx++] = Convert.ToByte('L'); if (waitingForAck) { WaitForAck(1000); } if (!CheckedWrite(restart, 0, idx)) { return(false); } waitingForAck = true; } // send colSpan and rowSpan byte[] spans = new byte[62]; // buffer to be sent idx = BitBashing.SIntToDecimalBytes(ref spans, 0, 5, 0); spans[idx++] = Convert.ToByte(':'); spans[idx++] = Convert.ToByte('S'); // "Span Size" command idx += BitBashing.UIntToHexBytes(ref spans, idx, colSpan, 2, 2); idx += BitBashing.UIntToHexBytes(ref spans, idx, rowSpan, 2, 2); if (waitingForAck) { WaitForAck(1000); } if (!CheckedWrite(spans, 0, idx)) { return(false); } waitingForAck = true; // determine the necessity of a palette and send the palette bool usePalette = btn.palette != null && colSpan < 16 && rowSpan < 16; if (usePalette && startRow == 0 && btn.doUpdatePalette) { SendColorPalette(btn.palette); btn.SetDoUpdatePalette(false); } // send packetized line color data byte[] linePart = new byte[62]; // buffer to be sent int linePartLen; // packetized line message length int bytesPerPixel = (usePalette) ? 1 : 2; linePartLen = (int)(56 / bytesPerPixel); char command = (usePalette) ? 'l' : 'L'; for (int i = 0; i < line.Length; i += linePartLen) { int sendCnt = Math.Min(linePartLen, line.Length - i); // prepare the message header int pixelCnt = (usePalette) ? sendCnt : sendCnt * bytesPerPixel; idx = BitBashing.SIntToDecimalBytes(ref linePart, 0, pixelCnt + 1, 0); linePart[idx++] = Convert.ToByte(':'); linePart[idx++] = Convert.ToByte(command); // "Line Part" or "line (palette) Part" command // add the color values for (int j = 0; j < sendCnt; j++) { int col = idx + j * bytesPerPixel; ushort color16 = ImageMagic.ColorConvert24To16(line[i + j]); if (usePalette) { linePart[col] = btn.palette.GetPaletteColor(color16); } else { linePart[col + 1] = BitConverter.GetBytes(color16)[0]; linePart[col + 0] = BitConverter.GetBytes(color16)[1]; } } // send the message if (waitingForAck) { WaitForAck(1000); } if (!CheckedWrite(linePart, 0, pixelCnt + idx)) { return(false); } waitingForAck = true; } //Console.WriteLine("c#: " + timer.ElapsedMilliseconds); return(true); }