From 7e27f063648a8cb4dd2c2886114e9c3fa50b2eeb Mon Sep 17 00:00:00 2001
From: Miguel Risco-Castillo <mriscoc@users.noreply.github.com>
Date: Mon, 6 Jun 2022 00:01:06 -0500
Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=B8=20ProUI=20G-code=20preview,=20PID?=
 =?UTF-8?q?=20plot=20(#24282)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 Marlin/src/lcd/e3v2/proui/base64.hpp        | 208 ++++++++++++++++
 Marlin/src/lcd/e3v2/proui/dwin.cpp          | 101 ++++++--
 Marlin/src/lcd/e3v2/proui/dwin_defines.h    |   2 +
 Marlin/src/lcd/e3v2/proui/gcode_preview.cpp | 253 ++++++++++++++++++++
 Marlin/src/lcd/e3v2/proui/gcode_preview.h   |  27 +++
 Marlin/src/lcd/e3v2/proui/plot.cpp          |  94 ++++++++
 Marlin/src/lcd/e3v2/proui/plot.h            |  54 +++++
 Marlin/src/lcd/e3v2/proui/ubl_tools.cpp     |  18 +-
 buildroot/tests/STM32F103RE_creality        |  11 +-
 buildroot/tests/STM32F401RC_creality        |  13 +-
 10 files changed, 749 insertions(+), 32 deletions(-)
 create mode 100644 Marlin/src/lcd/e3v2/proui/base64.hpp
 create mode 100644 Marlin/src/lcd/e3v2/proui/gcode_preview.cpp
 create mode 100644 Marlin/src/lcd/e3v2/proui/gcode_preview.h
 create mode 100644 Marlin/src/lcd/e3v2/proui/plot.cpp
 create mode 100644 Marlin/src/lcd/e3v2/proui/plot.h

diff --git a/Marlin/src/lcd/e3v2/proui/base64.hpp b/Marlin/src/lcd/e3v2/proui/base64.hpp
new file mode 100644
index 00000000000..d82d0b27e8a
--- /dev/null
+++ b/Marlin/src/lcd/e3v2/proui/base64.hpp
@@ -0,0 +1,208 @@
+/**
+ * Base64 encoder/decoder for arduino repo
+ * Uses common web conventions - '+' for 62, '/' for 63, '=' for padding.
+ * Note that invalid base64 characters are interpreted as padding.
+ * Author: Densaugeo
+ * Maintainer: Densaugeo
+ * Version: 1.2.1.1
+ * Changed unsigned int to uint16_t for use in the professional Ender 3V2/S1 firmware
+ * Url: https://www.arduino.cc/reference/en/libraries/base64/
+ */
+
+#ifndef BASE64_H_INCLUDED
+#define BASE64_H_INCLUDED
+
+/* binary_to_base64:
+ *   Description:
+ *     Converts a single byte from a binary value to the corresponding base64 character
+ *   Parameters:
+ *     v - Byte to convert
+ *   Returns:
+ *     ascii code of base64 character. If byte is >= 64, then there is not corresponding base64 character
+ *     and 255 is returned
+ */
+unsigned char binary_to_base64(unsigned char v);
+
+/* base64_to_binary:
+ *   Description:
+ *     Converts a single byte from a base64 character to the corresponding binary value
+ *   Parameters:
+ *     c - Base64 character (as ascii code)
+ *   Returns:
+ *     6-bit binary value
+ */
+unsigned char base64_to_binary(unsigned char c);
+
+/* encode_base64_length:
+ *   Description:
+ *     Calculates length of base64 string needed for a given number of binary bytes
+ *   Parameters:
+ *     input_length - Amount of binary data in bytes
+ *   Returns:
+ *     Number of base64 characters needed to encode input_length bytes of binary data
+ */
+uint16_t encode_base64_length(uint16_t input_length);
+
+/* decode_base64_length:
+ *   Description:
+ *     Calculates number of bytes of binary data in a base64 string
+ *     Variant that does not use input_length no longer used within library, retained for API compatibility
+ *   Parameters:
+ *     input - Base64-encoded null-terminated string
+ *     input_length (optional) - Number of bytes to read from input pointer
+ *   Returns:
+ *     Number of bytes of binary data in input
+ */
+uint16_t decode_base64_length(unsigned char input[]);
+uint16_t decode_base64_length(unsigned char input[], uint16_t input_length);
+
+/* encode_base64:
+ *   Description:
+ *     Converts an array of bytes to a base64 null-terminated string
+ *   Parameters:
+ *     input - Pointer to input data
+ *     input_length - Number of bytes to read from input pointer
+ *     output - Pointer to output string. Null terminator will be added automatically
+ *   Returns:
+ *     Length of encoded string in bytes (not including null terminator)
+ */
+uint16_t encode_base64(unsigned char input[], uint16_t input_length, unsigned char output[]);
+
+/* decode_base64:
+ *   Description:
+ *     Converts a base64 null-terminated string to an array of bytes
+ *   Parameters:
+ *     input - Pointer to input string
+ *     input_length (optional) - Number of bytes to read from input pointer
+ *     output - Pointer to output array
+ *   Returns:
+ *     Number of bytes in the decoded binary
+ */
+uint16_t decode_base64(unsigned char input[], unsigned char output[]);
+uint16_t decode_base64(unsigned char input[], uint16_t input_length, unsigned char output[]);
+
+unsigned char binary_to_base64(unsigned char v) {
+  // Capital letters - 'A' is ascii 65 and base64 0
+  if (v < 26) return v + 'A';
+
+  // Lowercase letters - 'a' is ascii 97 and base64 26
+  if (v < 52) return v + 71;
+
+  // Digits - '0' is ascii 48 and base64 52
+  if (v < 62) return v - 4;
+
+  // '+' is ascii 43 and base64 62
+  if (v == 62) return '+';
+
+  // '/' is ascii 47 and base64 63
+  if (v == 63) return '/';
+
+  return 64;
+}
+
+unsigned char base64_to_binary(unsigned char c) {
+  // Capital letters - 'A' is ascii 65 and base64 0
+  if ('A' <= c && c <= 'Z') return c - 'A';
+
+  // Lowercase letters - 'a' is ascii 97 and base64 26
+  if ('a' <= c && c <= 'z') return c - 71;
+
+  // Digits - '0' is ascii 48 and base64 52
+  if ('0' <= c && c <= '9') return c + 4;
+
+  // '+' is ascii 43 and base64 62
+  if (c == '+') return 62;
+
+  // '/' is ascii 47 and base64 63
+  if (c == '/') return 63;
+
+  return 255;
+}
+
+uint16_t encode_base64_length(uint16_t input_length) {
+  return (input_length + 2)/3*4;
+}
+
+uint16_t decode_base64_length(unsigned char input[]) {
+  return decode_base64_length(input, -1);
+}
+
+uint16_t decode_base64_length(unsigned char input[], uint16_t input_length) {
+  unsigned char *start = input;
+
+  while (base64_to_binary(input[0]) < 64 && (unsigned char)(input - start) < input_length) {
+    ++input;
+  }
+
+  input_length = input - start;
+  return input_length/4*3 + (input_length % 4 ? input_length % 4 - 1 : 0);
+}
+
+uint16_t encode_base64(unsigned char input[], uint16_t input_length, unsigned char output[]) {
+  uint16_t full_sets = input_length/3;
+
+  // While there are still full sets of 24 bits...
+  for (uint16_t i = 0; i < full_sets; ++i) {
+    output[0] = binary_to_base64(                         input[0] >> 2);
+    output[1] = binary_to_base64((input[0] & 0x03) << 4 | input[1] >> 4);
+    output[2] = binary_to_base64((input[1] & 0x0F) << 2 | input[2] >> 6);
+    output[3] = binary_to_base64( input[2] & 0x3F);
+
+    input += 3;
+    output += 4;
+  }
+
+  switch(input_length % 3) {
+    case 0:
+      output[0] = '\0';
+      break;
+    case 1:
+      output[0] = binary_to_base64(                         input[0] >> 2);
+      output[1] = binary_to_base64((input[0] & 0x03) << 4);
+      output[2] = '=';
+      output[3] = '=';
+      output[4] = '\0';
+      break;
+    case 2:
+      output[0] = binary_to_base64(                         input[0] >> 2);
+      output[1] = binary_to_base64((input[0] & 0x03) << 4 | input[1] >> 4);
+      output[2] = binary_to_base64((input[1] & 0x0F) << 2);
+      output[3] = '=';
+      output[4] = '\0';
+      break;
+  }
+
+  return encode_base64_length(input_length);
+}
+
+uint16_t decode_base64(unsigned char input[], unsigned char output[]) {
+  return decode_base64(input, -1, output);
+}
+
+uint16_t decode_base64(unsigned char input[], uint16_t input_length, unsigned char output[]) {
+  uint16_t output_length = decode_base64_length(input, input_length);
+
+  // While there are still full sets of 24 bits...
+  for (uint16_t i = 2; i < output_length; i += 3) {
+    output[0] = base64_to_binary(input[0]) << 2 | base64_to_binary(input[1]) >> 4;
+    output[1] = base64_to_binary(input[1]) << 4 | base64_to_binary(input[2]) >> 2;
+    output[2] = base64_to_binary(input[2]) << 6 | base64_to_binary(input[3]);
+
+    input += 4;
+    output += 3;
+  }
+
+  switch(output_length % 3) {
+    case 1:
+      output[0] = base64_to_binary(input[0]) << 2 | base64_to_binary(input[1]) >> 4;
+      break;
+    case 2:
+      output[0] = base64_to_binary(input[0]) << 2 | base64_to_binary(input[1]) >> 4;
+      output[1] = base64_to_binary(input[1]) << 4 | base64_to_binary(input[2]) >> 2;
+      break;
+  }
+
+  return output_length;
+}
+
+#endif // ifndef
diff --git a/Marlin/src/lcd/e3v2/proui/dwin.cpp b/Marlin/src/lcd/e3v2/proui/dwin.cpp
index 709ed05fed7..c98fd6a0e15 100644
--- a/Marlin/src/lcd/e3v2/proui/dwin.cpp
+++ b/Marlin/src/lcd/e3v2/proui/dwin.cpp
@@ -98,6 +98,14 @@
   #include "endstop_diag.h"
 #endif
 
+#if HAS_PIDPLOT
+  #include "plot.h"
+#endif
+
+#if HAS_GCODE_PREVIEW
+  #include "gcode_preview.h"
+#endif
+
 #if HAS_MESH
   #include "meshviewer.h"
 #endif
@@ -652,13 +660,19 @@ void Draw_PrintDone() {
   Title.ShowCaption(GET_TEXT_F(MSG_PRINT_DONE));
   DWINUI::ClearMainArea();
   DWIN_Print_Header(nullptr);
-  Draw_Print_ProgressBar();
-  Draw_Print_Labels();
-  DWINUI::Draw_Icon(ICON_PrintTime, 15, 173);
-  DWINUI::Draw_Icon(ICON_RemainTime, 150, 171);
-  Draw_Print_ProgressElapsed();
-  Draw_Print_ProgressRemain();
-  DWINUI::Draw_Button(BTN_Continue, 86, 273);
+  if (sdprint && TERN0(HAS_GCODE_PREVIEW, Preview_Valid())) {
+    DWIN_ICON_Show(0, 0, 1, 21, 100, 0x00);
+    DWINUI::Draw_Button(BTN_Continue, 86, 300);
+  }
+  else {
+    Draw_Print_ProgressBar();
+    Draw_Print_Labels();
+    DWINUI::Draw_Icon(ICON_PrintTime, 15, 173);
+    DWINUI::Draw_Icon(ICON_RemainTime, 150, 171);
+    Draw_Print_ProgressElapsed();
+    Draw_Print_ProgressRemain();
+    DWINUI::Draw_Button(BTN_Continue, 86, 273);
+  }
 }
 
 void Goto_PrintDone() {
@@ -1354,6 +1368,9 @@ void EachMomentUpdate() {
     #if HAS_ESDIAG
       if (checkkey == ESDiagProcess) ESDiag.Update();
     #endif
+    #if HAS_PIDPLOT
+      if (checkkey == PidProcess) Plot.Update((HMI_value.pidresult == PID_EXTR_START) ? thermalManager.wholeDegHotend(0) : thermalManager.wholeDegBed());
+    #endif
   }
 
   #if HAS_STATUS_MESSAGE_TIMEOUT
@@ -1580,15 +1597,49 @@ void DWIN_LevelingDone() {
 #endif
 
 // PID process
+
+#if HAS_PIDPLOT
+  void DWIN_Draw_PIDPopup() {
+    frame_rect_t gfrm = {40, 180, DWIN_WIDTH - 80, 120};
+    DWINUI::ClearMainArea();
+    Draw_Popup_Bkgd();
+    DWINUI::Draw_CenteredString(HMI_data.PopupTxt_Color, 100, GET_TEXT_F(MSG_PID_AUTOTUNE));
+    DWINUI::Draw_String(HMI_data.PopupTxt_Color, gfrm.x, gfrm.y - DWINUI::fontHeight() - 4, F("PID target:    Celsius"));
+    switch (HMI_value.pidresult) {
+      case PID_EXTR_START:
+        DWINUI::Draw_CenteredString(HMI_data.PopupTxt_Color, 120, F("for Nozzle is running."));
+        Plot.Draw(gfrm, thermalManager.hotend_maxtemp[0], HMI_data.HotendPidT);
+        DWINUI::Draw_Int(HMI_data.PopupTxt_Color, 3, gfrm.x + 90, gfrm.y - DWINUI::fontHeight() - 4, HMI_data.HotendPidT);
+        break;
+      case PID_BED_START:
+        DWINUI::Draw_CenteredString(HMI_data.PopupTxt_Color, 120, F("for BED is running."));
+        Plot.Draw(gfrm, BED_MAXTEMP, HMI_data.BedPidT);
+        DWINUI::Draw_Int(HMI_data.PopupTxt_Color, 3, gfrm.x + 90, gfrm.y - DWINUI::fontHeight() - 4, HMI_data.BedPidT);
+        break;
+      default:
+        break;
+    }
+  }
+#endif
+
 void DWIN_PidTuning(pidresult_t result) {
+  HMI_value.pidresult = result;
   switch (result) {
     case PID_BED_START:
-      HMI_SaveProcessID(NothingToDo);
-      DWIN_Draw_Popup(ICON_TempTooHigh, GET_TEXT_F(MSG_PID_AUTOTUNE), F("for BED is running."));
+      HMI_SaveProcessID(PidProcess);
+      #if HAS_PIDPLOT
+        DWIN_Draw_PIDPopup();
+      #else
+        DWIN_Draw_Popup(ICON_TempTooHigh, GET_TEXT_F(MSG_PID_AUTOTUNE), F("for BED is running."));
+      #endif
       break;
     case PID_EXTR_START:
-      HMI_SaveProcessID(NothingToDo);
-      DWIN_Draw_Popup(ICON_TempTooHigh, GET_TEXT_F(MSG_PID_AUTOTUNE), F("for Nozzle is running."));
+      HMI_SaveProcessID(PidProcess);
+      #if HAS_PIDPLOT
+        DWIN_Draw_PIDPopup();
+      #else
+        DWIN_Draw_Popup(ICON_TempTooHigh, GET_TEXT_F(MSG_PID_AUTOTUNE), F("for Nozzle is running."));
+      #endif
       break;
     case PID_BAD_EXTRUDER_NUM:
       checkkey = last_checkkey;
@@ -1905,10 +1956,30 @@ void HMI_LockScreen() {
 }
 
 
-void Goto_ConfirmToPrint() {
-  card.openAndPrintFile(card.filename);
-  DWIN_Print_Started(true);
-}
+#if HAS_GCODE_PREVIEW
+
+  void onClick_ConfirmToPrint() {
+    if (HMI_flag.select_flag) {     // Confirm
+      card.openAndPrintFile(card.filename);
+      return DWIN_Print_Started(true);
+    }
+    else {                          // Cancel
+      DWIN_ResetStatusLine();
+      checkkey = SelectFile;
+      return Draw_Print_File_Menu();
+    }
+  }
+
+  void Goto_ConfirmToPrint() {
+    Goto_Popup(Preview_DrawFromSD, onClick_ConfirmToPrint);
+  }
+
+#else
+  void Goto_ConfirmToPrint() {
+    card.openAndPrintFile(card.filename);
+    DWIN_Print_Started(true);
+  }
+#endif
 
 #if HAS_ESDIAG
   void Draw_EndStopDiag() {
diff --git a/Marlin/src/lcd/e3v2/proui/dwin_defines.h b/Marlin/src/lcd/e3v2/proui/dwin_defines.h
index bfeb06d7634..cb7b2f8160d 100644
--- a/Marlin/src/lcd/e3v2/proui/dwin_defines.h
+++ b/Marlin/src/lcd/e3v2/proui/dwin_defines.h
@@ -35,6 +35,8 @@
 #include <stddef.h>
 
 #define HAS_ESDIAG 1
+#define HAS_PIDPLOT 1
+#define HAS_GCODE_PREVIEW 1
 #if defined(__STM32F1__) || defined(STM32F1)
   #define DASH_REDRAW 1
 #endif
diff --git a/Marlin/src/lcd/e3v2/proui/gcode_preview.cpp b/Marlin/src/lcd/e3v2/proui/gcode_preview.cpp
new file mode 100644
index 00000000000..951f469e1c6
--- /dev/null
+++ b/Marlin/src/lcd/e3v2/proui/gcode_preview.cpp
@@ -0,0 +1,253 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2022 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * DWIN g-code thumbnail preview
+ * Author: Miguel A. Risco-Castillo
+ * version: 2.1
+ * Date: 2021/06/19
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ * For commercial applications additional licenses can be requested
+ */
+
+#include "dwin_defines.h"
+
+#if HAS_GCODE_PREVIEW
+
+#include "../../../core/types.h"
+#include "../../marlinui.h"
+#include "../../../sd/cardreader.h"
+#include "dwin_lcd.h"
+#include "dwinui.h"
+#include "dwin.h"
+#include "dwin_popup.h"
+#include "base64.hpp"
+#include "gcode_preview.h"
+
+typedef struct {
+  char name[13] = "";   //8.3 + null
+  uint32_t thumbstart = 0;
+  int thumbsize = 0;
+  int thumbheight = 0;
+  int thumbwidth = 0;
+  uint8_t *thumbdata = nullptr;
+  float time = 0;
+  float filament = 0;
+  float layer = 0;
+  float width = 0;
+  float height = 0;
+  float length = 0;
+  void setname(const char * const fn);
+  void clear();
+} fileprop_t;
+fileprop_t fileprop;
+
+void fileprop_t::setname(const char * const fn) {
+  const uint8_t len = _MIN(sizeof(name) - 1, strlen(fn));
+  memcpy(&name[0], fn, len);
+  name[len] = '\0';
+}
+
+void fileprop_t::clear() {
+  fileprop.name[0] = '\0';
+  fileprop.thumbstart = 0;
+  fileprop.thumbsize = 0;
+  fileprop.thumbheight = 0;
+  fileprop.thumbwidth = 0;
+  fileprop.thumbdata = nullptr;
+  fileprop.time = 0;
+  fileprop.filament = 0;
+  fileprop.layer = 0;
+  fileprop.height = 0;
+  fileprop.width = 0;
+  fileprop.length = 0;
+}
+
+void Get_Value(char *buf, const char * const key, float &value) {
+  char num[10] = "";
+  char * posptr = 0;
+  uint8_t i = 0;
+  if (!!value) return;
+  posptr = strstr(buf, key);
+  if (posptr != nullptr) {
+    while (i < sizeof(num)) {
+      char c = posptr[0];
+      if (!ISEOL(c) && (c != 0)) {
+        if ((c > 47 && c < 58) || (c == '.')) num[i++] = c;
+        posptr++;
+      }
+      else {
+        num[i] = '\0';
+        value = atof(num);
+        return;
+      }
+    }
+  }
+}
+
+bool Has_Preview() {
+  const char * tbstart = "; thumbnail begin 230x180";
+  char * posptr = 0;
+  uint8_t nbyte = 1;
+  uint32_t indx = 0;
+  char buf[256];
+  float tmp = 0;
+
+  fileprop.clear();
+  fileprop.setname(card.filename);
+
+  card.openFileRead(fileprop.name);
+
+  while ((nbyte > 0) && (indx < 4 * sizeof(buf)) && !fileprop.thumbstart) {
+    nbyte = card.read(buf, sizeof(buf) - 1);
+    if (nbyte > 0) {
+      buf[nbyte] = '\0';
+      Get_Value(buf, ";TIME:", fileprop.time);
+      Get_Value(buf, ";Filament used:", fileprop.filament);
+      Get_Value(buf, ";Layer height:", fileprop.layer);
+      Get_Value(buf, ";MINX:", tmp);
+      Get_Value(buf, ";MAXX:", fileprop.width);
+      fileprop.width -= tmp;
+      tmp = 0;
+      Get_Value(buf, ";MINY:", tmp);
+      Get_Value(buf, ";MAXY:", fileprop.length);
+      fileprop.length -= tmp;
+      tmp = 0;
+      Get_Value(buf, ";MINZ:", tmp);
+      Get_Value(buf, ";MAXZ:", fileprop.height);
+      fileprop.height -= tmp;
+      posptr = strstr(buf, tbstart);
+      if (posptr != NULL) {
+        fileprop.thumbstart = indx + (posptr - &buf[0]);
+      }
+      else {
+        indx += _MAX(10, nbyte - (signed)strlen(tbstart));
+        card.setIndex(indx);
+      }
+    }
+  }
+
+  if (!fileprop.thumbstart) {
+    card.closefile();
+    LCD_MESSAGE_F("Thumbnail not found");
+    return 0;
+  }
+
+  // Get the size of the thumbnail
+  card.setIndex(fileprop.thumbstart + strlen(tbstart));
+  for (uint8_t i = 0; i < 16; i++) {
+    char c = card.get();
+    if (!ISEOL(c)) {
+      buf[i] = c;
+    }
+    else {
+      buf[i] = 0;
+      break;
+    }
+  }
+  fileprop.thumbsize = atoi(buf);
+
+  // Exit if there isn't a thumbnail
+  if (!fileprop.thumbsize) {
+    card.closefile();
+    LCD_MESSAGE_F("Invalid Thumbnail Size");
+    return 0;
+  }
+
+  uint16_t readed = 0;
+  uint8_t buf64[fileprop.thumbsize];
+
+  fileprop.thumbdata = new uint8_t[3 + 3 * (fileprop.thumbsize / 4)];  // Reserve space for the JPEG thumbnail
+
+  while (readed < fileprop.thumbsize) {
+    uint8_t c = card.get();
+    if (!ISEOL(c) && (c != ';') && (c != ' ')) {
+      buf64[readed] = c;
+      readed++;
+    }
+  }
+  card.closefile();
+  buf64[readed] = 0;
+
+  fileprop.thumbsize = decode_base64(buf64, fileprop.thumbdata);  card.closefile();
+  DWINUI::WriteToSRAM(0x00, fileprop.thumbsize, fileprop.thumbdata);
+  delete[] fileprop.thumbdata;
+  return true;
+}
+
+void Preview_DrawFromSD() {
+  if (Has_Preview()) {
+    char buf[46];
+    char str_1[6] = "";
+    char str_2[6] = "";
+    char str_3[6] = "";
+    DWIN_Draw_Rectangle(1, HMI_data.Background_Color, 0, 0, DWIN_WIDTH, STATUS_Y - 1);
+    if (fileprop.time) {
+      sprintf_P(buf, PSTR("Estimated time: %i:%02i"), (uint16_t)fileprop.time / 3600, ((uint16_t)fileprop.time % 3600) / 60);
+      DWINUI::Draw_String(20, 10, buf);
+    }
+    if (fileprop.filament) {
+      sprintf_P(buf, PSTR("Filament used: %s m"), dtostrf(fileprop.filament, 1, 2, str_1));
+      DWINUI::Draw_String(20, 30, buf);
+    }
+    if (fileprop.layer) {
+      sprintf_P(buf, PSTR("Layer height: %s mm"), dtostrf(fileprop.layer, 1, 2, str_1));
+      DWINUI::Draw_String(20, 50, buf);
+    }
+    if (fileprop.width) {
+      sprintf_P(buf, PSTR("Volume: %sx%sx%s mm"), dtostrf(fileprop.width, 1, 1, str_1), dtostrf(fileprop.length, 1, 1, str_2), dtostrf(fileprop.height, 1, 1, str_3));
+      DWINUI::Draw_String(20, 70, buf);
+    }
+    DWINUI::Draw_Button(BTN_Print, 26, 290);
+    DWINUI::Draw_Button(BTN_Cancel, 146, 290);
+    DWIN_ICON_Show(0, 0, 1, 21, 90, 0x00);
+    Draw_Select_Highlight(true, 290);
+    DWIN_UpdateLCD();
+  }
+  else {
+    HMI_flag.select_flag = 1;
+    wait_for_user = false;
+  }
+}
+
+bool Preview_Valid() {
+  return !!fileprop.thumbstart;
+}
+
+void Preview_Reset() {
+  fileprop.thumbsize = 0;
+}
+
+#endif // HAS_GCODE_PREVIEW
diff --git a/Marlin/src/lcd/e3v2/proui/gcode_preview.h b/Marlin/src/lcd/e3v2/proui/gcode_preview.h
new file mode 100644
index 00000000000..4417084a242
--- /dev/null
+++ b/Marlin/src/lcd/e3v2/proui/gcode_preview.h
@@ -0,0 +1,27 @@
+/**
+ * DWIN g-code thumbnail preview
+ * Author: Miguel A. Risco-Castillo
+ * version: 2.1
+ * Date: 2021/06/19
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ * For commercial applications additional licenses can be requested
+ */
+
+#pragma once
+
+void Preview_DrawFromSD();
+bool Preview_Valid();
+void Preview_Reset();
diff --git a/Marlin/src/lcd/e3v2/proui/plot.cpp b/Marlin/src/lcd/e3v2/proui/plot.cpp
new file mode 100644
index 00000000000..ebc685fa245
--- /dev/null
+++ b/Marlin/src/lcd/e3v2/proui/plot.cpp
@@ -0,0 +1,94 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2022 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * DWIN Single var plot
+ * Author: Miguel A. Risco-Castillo
+ * Version: 2.0
+ * Date: 2022/01/31
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ * For commercial applications additional licenses can be requested
+ */
+
+#include "../../../inc/MarlinConfigPre.h"
+
+#ifdef DWIN_LCD_PROUI
+
+#include "plot.h"
+
+#include "../../../core/types.h"
+#include "../../marlinui.h"
+#include "dwin_lcd.h"
+#include "dwinui.h"
+#include "dwin_popup.h"
+#include "dwin.h"
+
+#define Plot_Bg_Color RGB( 1, 12,  8)
+
+PlotClass Plot;
+
+uint16_t grphpoints, r, x2, y2 = 0;
+frame_rect_t grphframe = {0};
+float scale = 0;
+
+void PlotClass::Draw(const frame_rect_t frame, const float max, const float ref) {
+  grphframe = frame;
+  grphpoints = 0;
+  scale = frame.h / max;
+  x2 = frame.x + frame.w - 1;
+  y2 = frame.y + frame.h - 1;
+  r = round((y2) - ref * scale);
+  DWINUI::Draw_Box(1, Plot_Bg_Color, frame);
+  for (uint8_t i = 1; i < 4; i++)  if (i*50 < frame.w) DWIN_Draw_VLine(Line_Color, i*50 + frame.x, frame.y, frame.h);
+  DWINUI::Draw_Box(0, Color_White, DWINUI::ExtendFrame(frame, 1));
+  DWIN_Draw_HLine(Color_Red, frame.x, r, frame.w);
+}
+
+void PlotClass::Update(const float value) {
+  if (!scale) return;
+  uint16_t y = round((y2) - value * scale);
+  if (grphpoints < grphframe.w) {
+    DWIN_Draw_Point(Color_Yellow, 1, 1, grphpoints + grphframe.x, y);
+  }
+  else {
+    DWIN_Frame_AreaMove(1, 0, 1, Plot_Bg_Color, grphframe.x, grphframe.y, x2, y2);
+    if ((grphpoints % 50) == 0) DWIN_Draw_VLine(Line_Color, x2 - 1, grphframe.y + 1, grphframe.h - 2);
+    DWIN_Draw_Point(Color_Red, 1, 1, x2 - 1, r);
+    DWIN_Draw_Point(Color_Yellow, 1, 1, x2 - 1, y);
+  }
+  grphpoints++;
+}
+
+#endif // DWIN_LCD_PROUI
diff --git a/Marlin/src/lcd/e3v2/proui/plot.h b/Marlin/src/lcd/e3v2/proui/plot.h
new file mode 100644
index 00000000000..8522c530bd3
--- /dev/null
+++ b/Marlin/src/lcd/e3v2/proui/plot.h
@@ -0,0 +1,54 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2022 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * DWIN Single var plot
+ * Author: Miguel A. Risco-Castillo
+ * Version: 1.0
+ * Date: 2022/01/30
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ * For commercial applications additional licenses can be requested
+ */
+#pragma once
+
+#include "dwinui.h"
+
+class PlotClass {
+public:
+  void Draw(frame_rect_t frame, float max, float ref = 0);
+  void Update(float value);
+};
+
+extern PlotClass Plot;
diff --git a/Marlin/src/lcd/e3v2/proui/ubl_tools.cpp b/Marlin/src/lcd/e3v2/proui/ubl_tools.cpp
index 96a7f52becc..6c917570057 100644
--- a/Marlin/src/lcd/e3v2/proui/ubl_tools.cpp
+++ b/Marlin/src/lcd/e3v2/proui/ubl_tools.cpp
@@ -1,10 +1,9 @@
 /**
- * UBL Tools and Mesh Viewer for Pro UI
- * Version: 1.0.0
- * Date: 2022/04/13
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2022 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
  *
- * Original Author: Henri-J-Norden
- * Original Source: https://github.com/Jyers/Marlin/pull/126
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,6 +20,15 @@
  *
  */
 
+/**
+ * UBL Tools and Mesh Viewer for Pro UI
+ * Version: 1.0.0
+ * Date: 2022/04/13
+ *
+ * Original Author: Henri-J-Norden
+ * Original Source: https://github.com/Jyers/Marlin/pull/126
+ */
+
 #include "../../../inc/MarlinConfigPre.h"
 #include "ubl_tools.h"
 
diff --git a/buildroot/tests/STM32F103RE_creality b/buildroot/tests/STM32F103RE_creality
index a5d2899dd56..5e6d5f044ac 100755
--- a/buildroot/tests/STM32F103RE_creality
+++ b/buildroot/tests/STM32F103RE_creality
@@ -18,9 +18,14 @@ opt_disable DWIN_CREALITY_LCD
 opt_enable DWIN_CREALITY_LCD_JYERSUI AUTO_BED_LEVELING_BILINEAR PROBE_MANUALLY
 exec_test $1 $2 "Ender 3 v2 with JyersUI" "$3"
 
-use_example_configs "Creality/Ender-3 V2/CrealityV422/MarlinUI"
-opt_add SDCARD_EEPROM_EMULATION AUTO_BED_LEVELING_BILINEAR Z_SAFE_HOMING
-exec_test $1 $2 "Ender 3 v2 with MarlinUI" "$3"
+use_example_configs "Creality/Ender-3 S1"
+opt_disable DWIN_CREALITY_LCD Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN AUTO_BED_LEVELING_BILINEAR CONFIGURATION_EMBEDDING CANCEL_OBJECTS FWRETRACT
+opt_enable DWIN_LCD_PROUI INDIVIDUAL_AXIS_HOMING_SUBMENU LCD_SET_PROGRESS_MANUALLY STATUS_MESSAGE_SCROLLING \
+           SOUND_MENU_ITEM PRINTCOUNTER NOZZLE_PARK_FEATURE ADVANCED_PAUSE_FEATURE FILAMENT_RUNOUT_SENSOR \
+           BLTOUCH Z_SAFE_HOMING AUTO_BED_LEVELING_UBL MESH_EDIT_MENU \
+           LIMITED_MAX_FR_EDITING LIMITED_MAX_ACCEL_EDITING LIMITED_JERK_EDITING BAUD_RATE_GCODE
+opt_set PREHEAT_3_LABEL '"CUSTOM"' PREHEAT_3_TEMP_HOTEND 240 PREHEAT_3_TEMP_BED 60 PREHEAT_3_FAN_SPEED 128
+exec_test $1 $2 "Ender-3 S1 with ProUI" "$3"
 
 restore_configs
 opt_set MOTHERBOARD BOARD_CREALITY_V452 SERIAL_PORT 1
diff --git a/buildroot/tests/STM32F401RC_creality b/buildroot/tests/STM32F401RC_creality
index c7cd464df06..380711d061c 100755
--- a/buildroot/tests/STM32F401RC_creality
+++ b/buildroot/tests/STM32F401RC_creality
@@ -6,15 +6,10 @@
 # exit on first failure
 set -e
 
-use_example_configs "Creality/Ender-3 S1"
-opt_disable DWIN_CREALITY_LCD Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN AUTO_BED_LEVELING_BILINEAR CONFIGURATION_EMBEDDING CANCEL_OBJECTS FWRETRACT
-opt_enable DWIN_LCD_PROUI INDIVIDUAL_AXIS_HOMING_SUBMENU LCD_SET_PROGRESS_MANUALLY STATUS_MESSAGE_SCROLLING \
-           SOUND_MENU_ITEM PRINTCOUNTER NOZZLE_PARK_FEATURE ADVANCED_PAUSE_FEATURE FILAMENT_RUNOUT_SENSOR \
-           BLTOUCH Z_SAFE_HOMING AUTO_BED_LEVELING_UBL MESH_EDIT_MENU \
-           LIMITED_MAX_FR_EDITING LIMITED_MAX_ACCEL_EDITING LIMITED_JERK_EDITING BAUD_RATE_GCODE
-opt_set MOTHERBOARD BOARD_CREALITY_V24S1_301F4 \
-        PREHEAT_3_LABEL '"CUSTOM"' PREHEAT_3_TEMP_HOTEND 240 PREHEAT_3_TEMP_BED 60 PREHEAT_3_FAN_SPEED 128
-exec_test $1 $2 "Ender-3 S1 with ProUI" "$3"
+use_example_configs "Creality/Ender-3 V2/CrealityV422/MarlinUI"
+opt_add SDCARD_EEPROM_EMULATION AUTO_BED_LEVELING_BILINEAR Z_SAFE_HOMING
+opt_set MOTHERBOARD BOARD_CREALITY_V24S1_301F4
+exec_test $1 $2 "Ender 3 v2 with MarlinUI" "$3"
 
 # clean up
 restore_configs