333 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			333 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
| /**The MIT License (MIT)
 | |
| Copyright (c) 2015 by Daniel Eichhorn
 | |
| Permission is hereby granted, free of charge, to any person obtaining a copy
 | |
| of this software and associated documentation files (the "Software"), to deal
 | |
| in the Software without restriction, including without limitation the rights
 | |
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | |
| copies of the Software, and to permit persons to whom the Software is
 | |
| furnished to do so, subject to the following conditions:
 | |
| The above copyright notice and this permission notice shall be included in all
 | |
| copies or substantial portions of the Software.
 | |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | |
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | |
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | |
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | |
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | |
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | |
| SOFTWARE.
 | |
| See more at http://blog.squix.ch
 | |
| 
 | |
| Adapted by Bodmer to use the faster TFT_eSPI library:
 | |
| https://github.com/Bodmer/TFT_eSPI
 | |
| 
 | |
| Bodmer: Functions no longer needed weeded out, Jpeg decoder functions added
 | |
| Bodmer: drawBMP() updated to buffer in and out pixels and use screen CGRAM rotation for faster bottom up drawing (now ~2x faster)
 | |
| */
 | |
| 
 | |
| #include "GfxUi.h"
 | |
| 
 | |
| GfxUi::GfxUi(TFT_eSPI *tft) {
 | |
|   _tft = tft;
 | |
| }
 | |
| 
 | |
| void GfxUi::drawProgressBar(uint16_t x0, uint16_t y0, uint16_t w, uint16_t h, uint8_t percentage, uint16_t frameColor, uint16_t barColor) {
 | |
|   if (percentage == 0) {
 | |
|     _tft->fillRoundRect(x0, y0, w, h, 3, TFT_BLACK);
 | |
|   }
 | |
|   uint8_t margin = 2;
 | |
|   uint16_t barHeight = h - 2 * margin;
 | |
|   uint16_t barWidth = w - 2 * margin;
 | |
|   _tft->drawRoundRect(x0, y0, w, h, 3, frameColor);
 | |
|   _tft->fillRect(x0 + margin, y0 + margin, barWidth * percentage / 100.0, barHeight, barColor);
 | |
| }
 | |
| 
 | |
| // This drawBMP function contains code from:
 | |
| // https://github.com/adafruit/Adafruit_ILI9341/blob/master/examples/spitftbitmap/spitftbitmap.ino
 | |
| // Here is Bodmer's version: this uses the ILI9341 CGRAM coordinate rotation features inside the display and
 | |
| // buffers both file and TFT pixel blocks, it typically runs about 2x faster for bottom up encoded BMP images
 | |
| 
 | |
| //void GfxUi::drawBMP(String filename, uint8_t x, uint16_t y, boolean flip) { // Alernative for caller control of flip
 | |
| void GfxUi::drawBmp(String filename, uint8_t x, uint16_t y) {
 | |
|   // Flips the TFT internal SGRAM coords to draw bottom up BMP images faster, in this application it can be fixed
 | |
|   boolean flip = 1;
 | |
| 
 | |
|   if ((x >= _tft->width()) || (y >= _tft->height())) return;
 | |
| 
 | |
|   fs::File bmpFile;
 | |
|   int16_t  bmpWidth, bmpHeight;   // Image W+H in pixels
 | |
|   uint32_t bmpImageoffset;        // Start address of image data in file
 | |
|   uint32_t rowSize;               // Not always = bmpWidth; may have padding
 | |
|   uint8_t  sdbuffer[3 * BUFFPIXEL];    // file read pixel buffer (8 bits each R+G+B per pixel)
 | |
|   uint16_t tftbuffer[BUFFPIXEL];       // TFT pixel out buffer (16-bit per pixel)
 | |
|   uint8_t  rgb_ptr = sizeof(sdbuffer); // read 24 bit RGB pixel data buffer pointer (8 bit so BUFF_SIZE must be less than 86)
 | |
|   boolean  goodBmp = false;            // Flag set to true on valid header parse
 | |
|   int16_t  w, h, row, col;             // to store width, height, row and column
 | |
|   uint8_t rotation;      // to restore rotation
 | |
|   uint8_t  tft_ptr = 0;  // TFT 16 bit 565 format pixel data buffer pointer
 | |
| 
 | |
|   // Check file exists and open it
 | |
|   Serial.println(filename);
 | |
|   if ( !(bmpFile = SPIFFS.open(filename, "r")) ) {
 | |
|     Serial.println(F(" File not found")); // Can comment out if not needed
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Parse BMP header to get the information we need
 | |
|   if (read16(bmpFile) == 0x4D42) { // BMP file start signature check
 | |
|     read32(bmpFile);       // Dummy read to throw away and move on
 | |
|     read32(bmpFile);       // Read & ignore creator bytes
 | |
|     bmpImageoffset = read32(bmpFile); // Start of image data
 | |
|     read32(bmpFile);       // Dummy read to throw away and move on
 | |
|     bmpWidth  = read32(bmpFile);  // Image width
 | |
|     bmpHeight = read32(bmpFile);  // Image height
 | |
| 
 | |
|     // Only proceed if we pass a bitmap file check
 | |
|     // Number of image planes -- must be '1', depth 24 and 0 (uncompressed format)
 | |
|     if ((read16(bmpFile) == 1) && (read16(bmpFile) == 24) && (read32(bmpFile) == 0)) {
 | |
|       goodBmp = true; // Supported BMP format
 | |
|       // BMP rows are padded (if needed) to 4-byte boundary
 | |
|       rowSize = (bmpWidth * 3 + 3) & ~3;
 | |
|       // Crop area to be loaded
 | |
|       w = bmpWidth;
 | |
|       h = bmpHeight;
 | |
| 
 | |
|       // We might need to alter rotation to avoid tedious file pointer manipulation
 | |
|       // Save the current value so we can restore it later
 | |
|       rotation = _tft->getRotation();
 | |
|       // Use TFT SGRAM coord rotation if flip is set for 25% faster rendering (new rotations 4-7 supported by library)
 | |
|       if (flip) _tft->setRotation((rotation + (flip<<2)) % 8); // Value 0-3 mapped to 4-7
 | |
| 
 | |
|       // Calculate new y plot coordinate if we are flipping
 | |
|       switch (rotation) {
 | |
|         case 0:
 | |
|           if (flip) y = _tft->height() - y - h; break;
 | |
|         case 1:
 | |
|           y = _tft->height() - y - h; break;
 | |
|           break;
 | |
|         case 2:
 | |
|           if (flip) y = _tft->height() - y - h; break;
 | |
|           break;
 | |
|         case 3:
 | |
|           y = _tft->height() - y - h; break;
 | |
|           break;
 | |
|       }
 | |
| 
 | |
|       // Set TFT address window to image bounds
 | |
|       // Currently, image will not draw or will be corrputed if it does not fit
 | |
|       // TODO -> efficient clipping, but I don't need it to be idiot proof ;-)
 | |
|       _tft->setAddrWindow(x, y, x + w - 1, y + h - 1);
 | |
| 
 | |
|       // Finally we are ready to send rows of pixels, writing like this avoids slow 32 bit multiply in 8 bit processors
 | |
|       for (uint32_t pos = bmpImageoffset; pos < bmpImageoffset + h * rowSize ; pos += rowSize) {
 | |
|         // Seek if we need to on boundaries and arrange to dump buffer and start again
 | |
|         if (bmpFile.position() != pos) {
 | |
|           bmpFile.seek(pos, fs::SeekSet);
 | |
|           rgb_ptr = sizeof(sdbuffer);
 | |
|           //Serial.println("Seeking in file >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
 | |
|         }
 | |
| 
 | |
|         // Fill the pixel buffer and plot
 | |
|         for (col = 0; col < w; col++) { // For each column...
 | |
|           // Time to read more pixel data?
 | |
|           if (rgb_ptr >= sizeof(sdbuffer)) {
 | |
|             // Push tft buffer to the display
 | |
|             if (tft_ptr) {
 | |
|               // Here we are sending a uint16_t array to the function
 | |
|               _tft->pushColors(tftbuffer, tft_ptr);
 | |
|               tft_ptr = 0; // tft_ptr and rgb_ptr are not always in sync...
 | |
|             }
 | |
|             // Finally reading bytes from SD Card
 | |
|             bmpFile.read(sdbuffer, sizeof(sdbuffer));
 | |
|             rgb_ptr = 0; // Set buffer index to start
 | |
|           }
 | |
|           // Convert pixel from BMP 8+8+8 format to TFT compatible 16 bit word
 | |
|           // Blue 5 bits, green 6 bits and red 5 bits (16 bits total)
 | |
|           // Is is a long line but it is faster than calling a library fn for this
 | |
|           tftbuffer[tft_ptr] = (sdbuffer[rgb_ptr++] >> 3) ;
 | |
|           tftbuffer[tft_ptr] |= ((sdbuffer[rgb_ptr++] & 0xFC) << 3);
 | |
|           tftbuffer[tft_ptr] |= ((sdbuffer[rgb_ptr++] & 0xF8) << 8);
 | |
|           tft_ptr++;
 | |
|         } // Next row
 | |
|       }   // All rows done
 | |
| 
 | |
|       // Write any partially full buffer to TFT
 | |
|       if (tft_ptr) _tft->pushColors(tftbuffer, tft_ptr);
 | |
| 
 | |
|     } // End of bitmap access
 | |
|   }   // End of bitmap file check
 | |
| 
 | |
|   bmpFile.close();
 | |
|   if(!goodBmp) Serial.println(F("BMP format not recognised."));
 | |
|   _tft->setRotation(rotation); // Put back original rotation
 | |
| }
 | |
| 
 | |
| // These read 16- and 32-bit types from the SD card file.
 | |
| // BMP data is stored little-endian, Arduino is little-endian too.
 | |
| // May need to reverse subscript order if porting elsewhere.
 | |
| 
 | |
| uint16_t GfxUi::read16(fs::File &f) {
 | |
|   uint16_t result;
 | |
|   ((uint8_t *)&result)[0] = f.read(); // LSB
 | |
|   ((uint8_t *)&result)[1] = f.read(); // MSB
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| uint32_t GfxUi::read32(fs::File &f) {
 | |
|   uint32_t result;
 | |
|   ((uint8_t *)&result)[0] = f.read(); // LSB
 | |
|   ((uint8_t *)&result)[1] = f.read();
 | |
|   ((uint8_t *)&result)[2] = f.read();
 | |
|   ((uint8_t *)&result)[3] = f.read(); // MSB
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*====================================================================================
 | |
|   This sketch support functions to render the Jpeg images.
 | |
| 
 | |
|   Created by Bodmer 15th Jan 2017
 | |
|   ==================================================================================*/
 | |
| 
 | |
| // Return the minimum of two values a and b
 | |
| #define minimum(a,b)     (((a) < (b)) ? (a) : (b))
 | |
| 
 | |
| #define USE_SPI_BUFFER // Comment out to use slower 16 bit pushColor()
 | |
| 
 | |
| //====================================================================================
 | |
| //   Opens the image file and prime the Jpeg decoder
 | |
| //====================================================================================
 | |
| void GfxUi::drawJpeg(const char *filename, int xpos, int ypos) {
 | |
| 
 | |
|   Serial.println("===========================");
 | |
|   Serial.print("Drawing file: "); Serial.println(filename);
 | |
|   Serial.println("===========================");
 | |
| 
 | |
|   // Open the named file (the Jpeg decoder library will close it after rendering image)
 | |
|   fs::File jpegFile = SPIFFS.open( filename, "r");    // File handle reference for SPIFFS
 | |
|   //  File jpegFile = SD.open( filename, FILE_READ);  // or, file handle reference for SD library
 | |
|  
 | |
|   if ( !jpegFile ) {
 | |
|     Serial.print("ERROR: File \""); Serial.print(filename); Serial.println ("\" not found!");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Use one of the three following methods to initialise the decoder:
 | |
|   //boolean decoded = JpegDec.decodeFsFile(jpegFile); // Pass a SPIFFS file handle to the decoder,
 | |
|   //boolean decoded = JpegDec.decodeSdFile(jpegFile); // or pass the SD file handle to the decoder,
 | |
|   boolean decoded = JpegDec.decodeFsFile(filename);  // or pass the filename (leading / distinguishes SPIFFS files)
 | |
|                                    // Note: the filename can be a String or character array type
 | |
|   if (decoded) {
 | |
|     // print information about the image to the serial port
 | |
|     jpegInfo();
 | |
| 
 | |
|     // render the image onto the screen at given coordinates
 | |
|     jpegRender(xpos, ypos);
 | |
|   }
 | |
|   else {
 | |
|     Serial.println("Jpeg file format not supported!");
 | |
|   }
 | |
| }
 | |
| 
 | |
| //====================================================================================
 | |
| //   Decode and render the Jpeg image onto the TFT screen
 | |
| //====================================================================================
 | |
| void GfxUi::jpegRender(int xpos, int ypos) {
 | |
| 
 | |
|   // retrieve infomration about the image
 | |
|   uint16_t  *pImg;
 | |
|   uint16_t mcu_w = JpegDec.MCUWidth;
 | |
|   uint16_t mcu_h = JpegDec.MCUHeight;
 | |
|   uint32_t max_x = JpegDec.width;
 | |
|   uint32_t max_y = JpegDec.height;
 | |
| 
 | |
|   // Jpeg images are draw as a set of image block (tiles) called Minimum Coding Units (MCUs)
 | |
|   // Typically these MCUs are 16x16 pixel blocks
 | |
|   // Determine the width and height of the right and bottom edge image blocks
 | |
|   uint32_t min_w = minimum(mcu_w, max_x % mcu_w);
 | |
|   uint32_t min_h = minimum(mcu_h, max_y % mcu_h);
 | |
| 
 | |
|   // save the current image block size
 | |
|   uint32_t win_w = mcu_w;
 | |
|   uint32_t win_h = mcu_h;
 | |
| 
 | |
|   // record the current time so we can measure how long it takes to draw an image
 | |
|   uint32_t drawTime = millis();
 | |
| 
 | |
|   // save the coordinate of the right and bottom edges to assist image cropping
 | |
|   // to the screen size
 | |
|   max_x += xpos;
 | |
|   max_y += ypos;
 | |
| 
 | |
|   // read each MCU block until there are no more
 | |
| #ifdef USE_SPI_BUFFER
 | |
|   while( JpegDec.readSwappedBytes()){ // Swap byte order so the SPI buffer can be used
 | |
| #else
 | |
|   while ( JpegDec.read()) { // Normal byte order read
 | |
| #endif
 | |
|     // save a pointer to the image block
 | |
|     pImg = JpegDec.pImage;
 | |
| 
 | |
|     // calculate where the image block should be drawn on the screen
 | |
|     int mcu_x = JpegDec.MCUx * mcu_w + xpos;
 | |
|     int mcu_y = JpegDec.MCUy * mcu_h + ypos;
 | |
| 
 | |
|     // check if the image block size needs to be changed for the right and bottom edges
 | |
|     if (mcu_x + mcu_w <= max_x) win_w = mcu_w;
 | |
|     else win_w = min_w;
 | |
|     if (mcu_y + mcu_h <= max_y) win_h = mcu_h;
 | |
|     else win_h = min_h;
 | |
| 
 | |
|     // calculate how many pixels must be drawn
 | |
|     uint32_t mcu_pixels = win_w * win_h;
 | |
| 
 | |
|     // draw image MCU block only if it will fit on the screen
 | |
|     if ( ( mcu_x + win_w) <= _tft->width() && ( mcu_y + win_h) <= _tft->height())
 | |
|   {
 | |
| #ifdef USE_SPI_BUFFER
 | |
|       // Now set a MCU bounding window on the TFT to push pixels into (x, y, x + width - 1, y + height - 1)
 | |
|       _tft->setWindow(mcu_x, mcu_y, mcu_x + win_w - 1, mcu_y + win_h - 1);
 | |
|       // Write all MCU pixels to the TFT window
 | |
|       uint8_t *pImg8 = (uint8_t*)pImg;     // Convert 16 bit pointer to an 8 bit pointer
 | |
|       _tft->pushColors(pImg8, mcu_pixels*2); // Send bytes via 64 byte SPI port buffer
 | |
| #else
 | |
|       // Now set a MCU bounding window on the TFT to push pixels into (x, y, x + width - 1, y + height - 1)
 | |
|       _tft->setAddrWindow(mcu_x, mcu_y, mcu_x + win_w - 1, mcu_y + win_h - 1);
 | |
|       // Write all MCU pixels to the TFT window
 | |
|       while (mcu_pixels--) _tft->pushColor(*pImg++);
 | |
| #endif
 | |
|     }
 | |
| 
 | |
|     else if ( ( mcu_y + win_h) >= _tft->height()) JpegDec.abort();
 | |
| 
 | |
|   }
 | |
| 
 | |
|   // calculate how long it took to draw the image
 | |
|   drawTime = millis() - drawTime; // Calculate the time it took
 | |
| 
 | |
|   // print the results to the serial port
 | |
|   Serial.print  ("Total render time was    : "); Serial.print(drawTime); Serial.println(" ms");
 | |
|   Serial.println("=====================================");
 | |
| 
 | |
| }
 | |
| 
 | |
| //====================================================================================
 | |
| //   Print information decoded from the Jpeg image
 | |
| //====================================================================================
 | |
| void GfxUi::jpegInfo() {
 | |
| 
 | |
|   Serial.println("===============");
 | |
|   Serial.println("JPEG image info");
 | |
|   Serial.println("===============");
 | |
|   Serial.print  ("Width      :"); Serial.println(JpegDec.width);
 | |
|   Serial.print  ("Height     :"); Serial.println(JpegDec.height);
 | |
|   Serial.print  ("Components :"); Serial.println(JpegDec.comps);
 | |
|   Serial.print  ("MCU / row  :"); Serial.println(JpegDec.MCUSPerRow);
 | |
|   Serial.print  ("MCU / col  :"); Serial.println(JpegDec.MCUSPerCol);
 | |
|   Serial.print  ("Scan type  :"); Serial.println(JpegDec.scanType);
 | |
|   Serial.print  ("MCU width  :"); Serial.println(JpegDec.MCUWidth);
 | |
|   Serial.print  ("MCU height :"); Serial.println(JpegDec.MCUHeight);
 | |
|   Serial.println("===============");
 | |
|   Serial.println("");
 | |
| 
 | |
| }
 | |
| //====================================================================================
 |