diff --git a/examples/web_image_compressor/bin_to_c.cpp b/examples/web_image_compressor/bin_to_c.cpp
new file mode 100644
index 0000000..affc044
--- /dev/null
+++ b/examples/web_image_compressor/bin_to_c.cpp
@@ -0,0 +1,68 @@
+//
+// Convert binary data to C hex statements
+//
+#include
+//
+// Convert from bin to hex using the same buffer
+//
+int bin2c(char *leafname, uint8_t *pSrc, int iSrcSize, int w, int h)
+{
+int i, j, k, iCount, iSize = 0;
+char szTemp[128];
+uint8_t *p, *pTemp;
+const uint8_t u8Hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+ pTemp = (uint8_t *)malloc(iSrcSize);
+ if (!pTemp) return 0; // something went wrong
+ memcpy(pTemp, pSrc, iSrcSize); // keep original binary data here
+ sprintf(szTemp, "//\n// %d x %d 1-bpp G5 compressed image\n", w, h);
+ i = strlen(szTemp);
+ memcpy(&pSrc[iSize], szTemp, i);
+ iSize += i;
+ sprintf(szTemp, "// data size = %d bytes\n//\n", iSrcSize);
+ i = strlen(szTemp);
+ memcpy(&pSrc[iSize], szTemp, i);
+ iSize += i;
+ sprintf(szTemp, "// for non-Arduino builds...\n#ifndef PROGMEM\n#define PROGMEM\n#endif\nconst uint8_t %s[] PROGMEM = {\n", leafname); // start of data array
+ i = strlen(szTemp);
+ memcpy(&pSrc[iSize], szTemp, i);
+ iSize += i;
+ // convert the data
+ iCount = 0;
+ p = pTemp;
+ for (i=0; i>4; i++) { // do lines of 16 bytes
+ pSrc[iSize++] = '\t';
+ for (j=0; j<16; j++) {
+ pSrc[iSize++] = '0';
+ pSrc[iSize++] = 'x';
+ pSrc[iSize++] = u8Hex[p[(i*16)+j]>>4];
+ pSrc[iSize++] = u8Hex[p[(i*16)+j] & 0xf];
+ if (iCount != iSrcSize-1) { // add a comma
+ pSrc[iSize++] = ',';
+ }
+ iCount++;
+ }
+ pSrc[iSize++] = '\n';
+ } // for each data byte
+ p += (iSrcSize & 0xfff0); // point to last section
+ if (iSrcSize & 0xf) // any remaining characters?
+ {
+ pSrc[iSize++] = '\t';
+ for (j=0; j<(iSrcSize & 0xf); j++) {
+ pSrc[iSize++] = '0';
+ pSrc[iSize++] = 'x';
+ pSrc[iSize++] = u8Hex[p[(i*16)+j]>>4];
+ pSrc[iSize++] = u8Hex[p[(i*16)+j] & 0xf];
+ if (iCount != iSrcSize-1) { // add a comma
+ pSrc[iSize++] = ',';
+ }
+ iCount++;
+ }
+ }
+ // finalize
+ pSrc[iSize++] = '}';
+ pSrc[iSize++] = ';';
+ pSrc[iSize++] = '\n'; // final closing brace
+
+ return iSize;
+} /* bin2c() */
+
diff --git a/examples/web_image_compressor/web_image_compressor.ino b/examples/web_image_compressor/web_image_compressor.ino
new file mode 100644
index 0000000..7e166de
--- /dev/null
+++ b/examples/web_image_compressor/web_image_compressor.ino
@@ -0,0 +1,301 @@
+#include
+#include // ESPAsyncWebSrv by dvarrel
+#include
+
+const String default_ssid = "your_ssid";
+const String default_wifipassword = "your_password";
+const int default_webserverporthttp = 80;
+int iWidth, iHeight, iG5Size;
+bool bHexOutput;
+uint8_t *pBuffer, *pOut;
+char szLeafName[128];
+int iFileSize;
+G5ENCODER g5enc;
+BB_BITMAP bbbm;
+int bin2c(char *leafname, uint8_t *pSrc, int iSrcSize, int w, int h);
+
+const char index_html[] PROGMEM = R"rawliteral(
+
+
+
+
+
+
+
+ ESP32 BMP File Compress Utility
+ Upload a 1-bit per pixel Windows BMP file and this utility will compress it into a Group5 compressed bitmap file
+
+
+ Download type: %TYPE%
+ Image Dimensions: %IMAGESIZE% | Compressed Size: %G5SIZE%
+ After clicking upload it will take some time for the file to upload,
+ there is no indicator that the upload began. Please be patient.
+ Once uploaded the page will refresh and image information will be displayed.
+ After uploading, press the download link to retrieve the compressed file.
+ %DOWNLOAD%
+
+
+)rawliteral";
+
+// configuration structure
+struct Config {
+ String ssid; // wifi ssid
+ String wifipassword; // wifi password
+ int webserverporthttp; // http port number for web admin
+};
+
+// variables
+Config config; // configuration
+AsyncWebServer *server; // initialise webserver
+
+//
+// Trim off the leaf name from a fully
+// formed file pathname
+//
+void GetLeafName(char *fname, char *leaf)
+{
+ int i, iLen;
+
+ iLen = (int)strlen(fname);
+ for (i=iLen-1; i>=0; i--)
+ {
+ if (fname[i] == '\\' || fname[i] == '/') // Windows or Linux
+ break;
+ }
+ strcpy(leaf, &fname[i+1]);
+ // remove the filename extension
+ iLen = (int)strlen(leaf);
+ for (i=iLen-1; i>=0; i--)
+ {
+ if (leaf[i] == '.')
+ {
+ leaf[i] = 0;
+ break;
+ }
+ }
+} /* GetLeafName() */
+
+uint32_t UnalignedRead32(uint8_t *s)
+{
+ uint32_t u32;
+ u32 = (uint32_t)s[0];
+ u32 += (uint32_t)s[1]<<8;
+ u32 += (uint32_t)s[2]<<16;
+ u32 += (uint32_t)s[3]<<24;
+ return u32;
+}
+uint16_t UnalignedRead16(uint8_t *s)
+{
+ uint16_t u16;
+ u16 = s[0];
+ u16 += ((uint16_t)s[1]<<8);
+ return u16;
+}
+// Read the file info and compress it with G5
+void ProcessFile(void)
+{
+int y, rc, ibpp, offset, bytewidth, iPitch, iDelta;
+uint8_t *s;
+
+ if (pBuffer[0] != 'B' || pBuffer[1] != 'M' || pBuffer[14] < 0x28) {
+ Serial.println("Not a Windows BMP file!");
+ return;
+ } else {
+ Serial.println("Valid WinBMP!");
+ }
+ iWidth = UnalignedRead32(&pBuffer[18]);
+ iHeight = UnalignedRead32(&pBuffer[22]);
+ ibpp = UnalignedRead16(&pBuffer[26]) * UnalignedRead16(&pBuffer[28]);
+ if (ibpp != 1) {
+ Serial.println("Not a 1-bpp Bitmap!");
+ return;
+ } else {
+ Serial.printf("%d x %d, %d bpp\n", iWidth, iHeight, ibpp);
+ }
+ offset = UnalignedRead32(&pBuffer[10]); // offset to bits
+ Serial.printf("offset to bits: %d\n", offset);
+ bytewidth = (iWidth+7) >> 3;
+ iPitch = (bytewidth + 3) & 0xfffc; // DWORD aligned
+ s = &pBuffer[offset];
+ iDelta = iPitch;
+ if (iHeight > 0) {
+ iDelta = -iPitch;
+ s = &pBuffer[offset + (iHeight-1) * iPitch];
+ } else {
+ iHeight = -iHeight;
+ }
+ Serial.printf("pitch = %d\n", iPitch);
+ rc = g5enc.init(iWidth, iHeight, &pOut[sizeof(BB_BITMAP)], iPitch * iHeight);
+ for (y=0; yclient()->remoteIP().toString() + + " " + request->url();
+ Serial.println(logmessage);
+ // See if the user wants the original binary file or a C hex version
+
+ AsyncWebServerResponse *response = request->beginChunkedResponse("appliction/octet-stream", [](uint8_t *buffer, size_t maxLen, size_t alreadySent) -> size_t {
+ if (iG5Size - alreadySent >= maxLen) {
+ memcpy(buffer, &pOut[alreadySent], maxLen);
+ return maxLen;
+ } else { // last chunk and then 0
+ memcpy(buffer, &pOut[alreadySent], iG5Size - alreadySent);
+ return iG5Size - alreadySent;
+ }
+ });
+ request->send(response);
+} /* handleDownload() */
+
+void setup() {
+ iFileSize = 0; // no image loaded
+ Serial.begin(115200);
+ Serial.println("Booting ...");
+ pBuffer = (uint8_t *)malloc(65536); // start with 64k
+ pOut = (uint8_t *)malloc(16384); // start with 16k for output
+ if (!pBuffer || !pOut) {
+ Serial.println("malloc failed!");
+ while (1) {};
+ }
+ Serial.println("\nLoading Configuration ...");
+
+ config.ssid = default_ssid;
+ config.wifipassword = default_wifipassword;
+ config.webserverporthttp = default_webserverporthttp;
+
+ Serial.print("\nConnecting to Wifi: ");
+ WiFi.begin(config.ssid.c_str(), config.wifipassword.c_str());
+ while (WiFi.status() != WL_CONNECTED) {
+ delay(500);
+ Serial.print(".");
+ }
+
+ Serial.println("\n\nNetwork Configuration:");
+ Serial.println("----------------------");
+ Serial.print(" SSID: "); Serial.println(WiFi.SSID());
+ Serial.print(" Wifi Status: "); Serial.println(WiFi.status());
+ Serial.print("Wifi Strength: "); Serial.print(WiFi.RSSI()); Serial.println(" dBm");
+ Serial.print(" MAC: "); Serial.println(WiFi.macAddress());
+ Serial.print(" IP: "); Serial.println(WiFi.localIP());
+ Serial.print(" Subnet: "); Serial.println(WiFi.subnetMask());
+ Serial.print(" Gateway: "); Serial.println(WiFi.gatewayIP());
+ Serial.print(" DNS 1: "); Serial.println(WiFi.dnsIP(0));
+ Serial.print(" DNS 2: "); Serial.println(WiFi.dnsIP(1));
+ Serial.print(" DNS 3: "); Serial.println(WiFi.dnsIP(2));
+
+ // configure web server
+ Serial.println("\nConfiguring Webserver ...");
+ server = new AsyncWebServer(config.webserverporthttp);
+ configureWebServer();
+
+ // startup web server
+ Serial.println("Starting Webserver ...");
+ server->begin();
+}
+
+void loop() {
+}
+
+void configureWebServer() {
+ server->on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
+ String logmessage = "Client:" + request->client()->remoteIP().toString() + + " " + request->url();
+ Serial.println(logmessage);
+ request->send_P(200, "text/html", index_html, processor);
+ });
+
+ // run handleDownload function when the output file is downloaded
+ server->on("/download", HTTP_GET, handleDownload);
+ server->on("/hex", HTTP_POST, handleHex);
+ // run handleUpload function when any file is uploaded
+ server->on("/upload", HTTP_POST, [](AsyncWebServerRequest *request) {
+ request->send(200);
+ }, handleUpload);
+}
+
+// handle hex toggle form
+void handleHex(AsyncWebServerRequest *request) {
+ bHexOutput = !bHexOutput; // toggle the setting
+ request->redirect("/");
+}
+// handles uploads
+void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
+ String logmessage = "Client:" + request->client()->remoteIP().toString() + " " + request->url();
+ Serial.println(logmessage);
+
+ //if (!index) {
+ // logmessage = "Upload Start: " + String(filename);
+ // open the file on first call and store the file handle in the request object
+ // request->_tempFile = SPIFFS.open("/" + filename, "w");
+ // Serial.println(logmessage);
+ //}
+ iWidth = iHeight = iG5Size = 0; // same for these
+
+ if (len) {
+ Serial.printf("incoming file data size = %d\n", len);
+ // stream the incoming chunk to the opened file
+// request->_tempFile.write(data, len);
+ logmessage = "Writing file: " + String(filename) + " index=" + String(index) + " len=" + String(len);
+ Serial.println(logmessage);
+ memcpy(&pBuffer[index], data, len);
+ iFileSize = index+len;
+ }
+
+ if (final) {
+ GetLeafName((char *)filename.c_str(), szLeafName);
+ Serial.print("Upload complete\n");
+// logmessage = "Upload Complete: " + String(filename) + ",size: " + String(index + len);
+ // close the file handle as the upload is now done
+// request->_tempFile.close();
+// Serial.println(logmessage);
+ ProcessFile();
+ request->redirect("/");
+ }
+}
+
+String processor(const String& var) {
+ char szTemp[256];
+ char szFile[128];
+ if (var == "DOWNLOAD") {
+ if (iG5Size == 0) {
+ strcpy(szTemp, "Download link will appear here after uploading a valid BMP file
");
+ } else {
+ sprintf(szFile, "%s.%s", szLeafName, (bHexOutput) ? "h" : "g5");
+ sprintf(szTemp, "Download %s", szFile, szFile);
+ }
+ return String(szTemp);
+ }
+ if (var == "IMAGESIZE") {
+ sprintf(szTemp, "%d x %d", iWidth, iHeight);
+ return String(szTemp);
+ }
+ if (var == "TYPE") {
+ if (bHexOutput) {
+ return String("C hex values");
+ } else {
+ return String("G5 binary data");
+ }
+ }
+ if (var == "G5SIZE") {
+ sprintf(szTemp, "%d bytes", iG5Size);
+ return String(szTemp);
+ }
+
+ return String();
+}