diff --git a/src/MAKERphone.cpp b/src/MAKERphone.cpp index 067d1b0..7c1c4c5 100644 --- a/src/MAKERphone.cpp +++ b/src/MAKERphone.cpp @@ -89,7 +89,6 @@ void MAKERphone::begin(bool splash) { display.setTextWrap(0); //setRotation(1); display.setTextSize(1); // landscape - buf.setColorDepth(8); // Set colour depth of Sprite to 8 (or 16) bits buf.createSprite(BUF2WIDTH, BUF2HEIGHT); // Create the sprite and clear background to black buf.setTextSize(1); @@ -4018,7 +4017,7 @@ int16_t MAKERphone::mp3Menu(const char* title, String* items, uint8_t length) { } void MAKERphone::listMP3(const char * dirname, uint8_t levels) { - /*mp3Count = 0; + mp3Count = 0; Serial.begin(115200); Serial.printf("Listing directory: %s\n", dirname); File root = SD.open(dirname); @@ -4034,7 +4033,10 @@ void MAKERphone::listMP3(const char * dirname, uint8_t levels) { uint8_t start = 0; File file = root.openNextFile(); while (file) { - String Name = file.name(); + char temp[50]; + file.getName(temp, 50); + String Name(temp); + Serial.println(Name); if (Name.endsWith(F(".MP3")) || Name.endsWith(F(".mp3"))) { Serial.print(counter); @@ -4046,7 +4048,41 @@ void MAKERphone::listMP3(const char * dirname, uint8_t levels) { counter++; } file = root.openNextFile(); - }*/ + } +} +void MAKERphone::listPhotos(const char * dirname, uint8_t levels) { + photoCount = 0; + Serial.begin(115200); + Serial.printf("Listing directory: %s\n", dirname); + File root = SD.open(dirname); + if (!root) { + Serial.println("Failed to open directory"); + return; + } + if (!root.isDirectory()) { + Serial.println("Not a directory"); + return; + } + int counter = 1; + uint8_t start = 0; + File file = root.openNextFile(); + while (file) { + char temp[50]; + file.getName(temp, 50); + String Name(temp); + Serial.println(Name); + if (Name.endsWith(F(".jpeg")) || Name.endsWith(F(".JPEG")) || Name.endsWith(F(".jpg")) || Name.endsWith(F(".JPG"))) + { + Serial.print(counter); + Serial.print(". "); + Serial.println(Name); + photoFiles[counter - 1] = Name; + Serial.println(Name); + photoCount++; + counter++; + } + file = root.openNextFile(); + } } void MAKERphone::mp3player(String songName) { /* Serial.begin(115200); @@ -4157,17 +4193,291 @@ void MAKERphone::mp3player(String songName) { } */ } void MAKERphone::mediaApp() { - /* if (!SD.begin(5, SD_SCK_MHZ(8))) - Serial.println("SD card error"); - listMP3(SD, "/", 0); - while (1) + while(1) { - int16_t index = mp3Menu("Select file to play:", mp3Files, mp3Count); - if (index == -1) + int8_t input = mediaMenu(mediaItems, 3); + + if(input == 0) + { + if (!SD.begin(5, SD_SCK_MHZ(8))) + Serial.println("SD card error"); + listMP3("/", 1); + while (1) + { + int16_t index = mp3Menu("Select file to play:", mp3Files, mp3Count); + if (index == -1) + break; + display.fillScreen(TFT_LIGHTGREY); + mp3player(mp3Files[index]); + } + } + if(input == 1) + { + while (!SD.begin(5, SD_SCK_MHZ(8))) + Serial.println("SD card error"); + listPhotos("/", 0); + while (1) + { + int16_t index = gui.menu("Select photo to open:", photoFiles, photoCount); + if (index == -1) + break; + Serial.println(index); + drawJpeg(photoFiles[index], 0, 0); + while(!buttons.released(BTN_A)) + update(); + } + } + } +} +void MAKERphone::drawJpeg(String 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) + File jpegFile = SD.open( filename, FILE_READ); // 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.decodeSdFile(jpegFile); // 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!"); + } +} +void MAKERphone::jpegRender(int xpos, int ypos) { + Serial.println("JPEG render"); + delay(5); + + // 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 drawn 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 + Serial.println("Here"); + delay(5); + uint32_t min_w = min(mcu_w, max_x % mcu_w); + + uint32_t min_h = min(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 + while (JpegDec.readSwappedBytes()) + { // Swap byte order so the SPI buffer can be used + + // 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; // Calculate coordinates of top left corner of current MCU + int mcu_y = JpegDec.MCUy * mcu_h + ypos; + + // check if the image block size needs to be changed for the right edge + if (mcu_x + mcu_w <= max_x) + win_w = mcu_w; + else + win_w = min_w; + + // check if the image block size needs to be changed for the bottom edge + if (mcu_y + mcu_h <= max_y) + win_h = mcu_h; + else + win_h = min_h; + + // copy pixels into a contiguous block + if (win_w != mcu_w) + { + uint16_t *cImg; + int p = 0; + cImg = pImg + win_w; + for (int h = 1; h < win_h; h++) + { + p += mcu_w; + for (int w = 0; w < win_w; w++) + { + *cImg = *(pImg + w + p); + cImg++; + } + } + } + + // draw image MCU block only if it will fit on the screen + if ((mcu_x + win_w) <= display.width() && (mcu_y + win_h) <= display.height()) + { + display.setSwapBytes(1); + display.pushImage(mcu_x, mcu_y, win_w, win_h, pImg); + } + + else if ((mcu_y + win_h) >= display.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("====================================="); + +} +void MAKERphone::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(""); +} +int8_t MAKERphone::mediaMenu(String* title, uint8_t length) { + bool pressed = 0; + uint8_t cursor = 0; + int32_t cameraY = 0; + int32_t cameraY_actual = 0; + dataRefreshFlag = 0; + + uint8_t boxHeight; + boxHeight = 40; //actually 2 less than that + while (1) { + while (!update()); + display.fillScreen(TFT_BLACK); + display.setCursor(0, 0); + cameraY_actual = (cameraY_actual + cameraY) / 2; + if (cameraY_actual - cameraY == 1) { + cameraY_actual = cameraY; + } + + for (uint8_t i = 0; i < length; i++) { + mediaMenuDrawBox(title[i], i, cameraY_actual); + } + mediaMenuDrawCursor(cursor, cameraY_actual, pressed); + + if (buttons.kpd.pin_read(JOYSTICK_B) == 1 && buttons.kpd.pin_read(JOYSTICK_D) == 1) + pressed = 0; + + if (buttons.released(BTN_A)) { //BUTTON CONFIRM + + while (!update());// Exit when pressed break; - display.fillScreen(TFT_LIGHTGREY); - mp3player(mp3Files[index]); - } */ + } + + if (buttons.released(JOYSTICK_D)) { //BUTTON UP + if (cursor == 0) { + cursor = length - 1; + if (length > 6) { + cameraY = -(cursor - 2) * boxHeight; + } + } + else { + cursor--; + if (cursor > 0 && (cursor * boxHeight + cameraY + settingsMenuYOffset) < boxHeight) { + cameraY += 15; + } + } + pressed = 1; + } + + if (buttons.released(JOYSTICK_B)) { //BUTTON DOWN + cursor++; + if ((cursor * boxHeight + cameraY + settingsMenuYOffset) > 128) { + cameraY -= boxHeight; + } + if (cursor >= length) { + cursor = 0; + cameraY = 0; + + } + pressed = 1; + } + + + if (buttons.released(BTN_B) == 1) //BUTTON BACK + { + return -1; + } + } + + return cursor; + +} +void MAKERphone::mediaMenuDrawBox(String title, uint8_t i, int32_t y) { + uint8_t scale; + uint8_t boxHeight; + scale = 2; + boxHeight = 40; + y += i * boxHeight + settingsMenuYOffset; + if (y < 0 || y > display.width()) { + return; + } + + + if (title == "Music") //red + { + display.fillRect(2, y + 1, display.width() - 4, boxHeight-2, TFT_DARKGREY); + } + if (title == "Photo") //green + { + display.fillRect(2, y + 1, display.width() - 4, boxHeight-2, TFT_DARKGREY); + } + if (title == "Video") //yellow + { + display.fillRect(2, y + 1, display.width() - 4, boxHeight-2, TFT_DARKGREY); + } + display.setTextColor(TFT_BLACK); + display.setTextSize(2); + display.setTextFont(2); + display.drawString(title, 60, y + 5); + display.setTextColor(TFT_WHITE); + display.setTextSize(1); +} +void MAKERphone::mediaMenuDrawCursor(uint8_t i, int32_t y, bool pressed) { + uint8_t boxHeight; + boxHeight = 40; + if (millis() % 500 <= 250 && pressed == 0) { + return; + } + y += i * boxHeight + settingsMenuYOffset; + display.drawRect(0, y-1, display.width()-1, boxHeight+2, TFT_RED); + display.drawRect(1, y, display.width()-3, boxHeight, TFT_RED); } void MAKERphone::MDCallback(void *cbData, const char *type, bool isUnicode, const char *string) { /* (void)cbData; @@ -4188,6 +4498,7 @@ void MAKERphone::MDCallback(void *cbData, const char *type, bool isUnicode, cons Serial.flush(); */} + //Settings app int8_t MAKERphone::settingsMenu(String* title, uint8_t length) { bool pressed = 0; @@ -4381,6 +4692,7 @@ bool MAKERphone::settingsApp() { while(!update()); } return false; + } } void MAKERphone::networkMenu() { uint8_t cursor = 0; @@ -4690,7 +5002,7 @@ void MAKERphone::displayMenu() { if (buttons.released(JOYSTICK_D)) { while (!update()); - if (cursor == 0) + if (cursor == 0) cursor = 2; else cursor--; @@ -6362,7 +6674,7 @@ void MAKERphone::loadSettings(bool debug) sleepTime = settings["sleep_time"]; backgroundIndex = settings["background_color"]; } else { - Serial.println("Error saving new settings"); + Serial.println("Error loading new settings"); } } @@ -6667,9 +6979,9 @@ int8_t GUI::menu(const char* title, String* items, uint8_t length) { } mp.display.print(title); - if (mp.buttons.kpd.pin_read(BTN_A) == 0) { //BUTTON CONFIRM + if (mp.buttons.released(BTN_A)) { //BUTTON CONFIRM - while (mp.buttons.kpd.pin_read(BTN_A) == 0);// Exit when pressed + while (!mp.update());// Exit when pressed break; } if (mp.buttons.kpd.pin_read(BTN_B) == 0) { //BUTTON BACK diff --git a/src/MAKERphone.h b/src/MAKERphone.h index 5617e3b..d80ab27 100644 --- a/src/MAKERphone.h +++ b/src/MAKERphone.h @@ -54,7 +54,7 @@ extern HardwareSerial Serial1; //Fonts and sprites to use #include "utility/Free_Fonts.h" #include "utility/sprites.c" - +#include "utility/JPEGDecoder.h" //Setup for MP3 playback @@ -271,7 +271,21 @@ class MAKERphone:public Buttons, public GUI void listMP3(const char * dirname, uint8_t levels); void mp3player(String songName); void mediaApp(); + int8_t mediaMenu(String* title, uint8_t length); + void mediaMenuDrawBox(String title, uint8_t i, int32_t y); + void mediaMenuDrawCursor(uint8_t i, int32_t y, bool pressed); void MDCallback(void *cbData, const char *type, bool isUnicode, const char *string); + String mediaItems[3] = { + "Music", + "Photo", + "Video", + }; + String photoFiles[255]; + void listPhotos(const char *dirname, uint8_t levels); + void drawJpeg(String filename, int xpos, int ypos); + void jpegRender(int xpos, int ypos); + void jpegInfo(); + uint8_t photoCount = 0; //Contacts app void contactsMenuDrawBox(String contact, String number, uint8_t i, int32_t y); diff --git a/src/TFT_eSPI/TFT_eSPI.cpp b/src/TFT_eSPI/TFT_eSPI.cpp index 0d568f4..84c2c8f 100644 --- a/src/TFT_eSPI/TFT_eSPI.cpp +++ b/src/TFT_eSPI/TFT_eSPI.cpp @@ -5201,7 +5201,6 @@ void TFT_eSprite::pushImage(int32_t x, int32_t y, uint32_t w, uint32_t h, uint1 } } - /*************************************************************************************** ** Function name: pushImage ** Description: push 565 colour FLASH (PROGMEM) image into a defined area diff --git a/src/utility/JPEGDecoder.h b/src/utility/JPEGDecoder.h new file mode 100644 index 0000000..0b8f9ab --- /dev/null +++ b/src/utility/JPEGDecoder.h @@ -0,0 +1 @@ +#include "JPEGDecoder/src/JPEGDecoder.h" \ No newline at end of file diff --git a/src/utility/JPEGDecoder/README.md b/src/utility/JPEGDecoder/README.md new file mode 100644 index 0000000..333b454 --- /dev/null +++ b/src/utility/JPEGDecoder/README.md @@ -0,0 +1,53 @@ +Arduino JPEGDecoder library +=========== + +This Arduino library supports the rendering of Jpeg files stored both on SD card and in arrays within program memory (FLASH) onto a TFT display. In addition images stored in the SPIFFS Flash filing system or "PROGMEM" arrays can be used with the ESP8266 processor. Use the ESP8266 board Core 2.3.0 in the Arduino IDE to avoid File definition conflicts if the SPIFFS and SD libraries are used together. + +The library works on the Arduino Due, ESP32 and ESP8266 (e.g. NodeMCU 1.0). Users have also reported success with the STM32 based processor boards. + +Example images can be found in the "extras" folder. + +Jpeg files in the "Progressive" format (where image data is compressed in multiple passes with progressively higher detail) are not supported since this would require much more memory, or too many Inverse Discrete Cosine Transform's for typical embedded systems. + +High Jpeg compression ratios work best on images with smooth colour changes, however the Baboon40.jpg image at only 23.8 KBytes renders quite nicely. Typically a 480x320 image can be compressed without much degradation to less than 32 KBytes, in comparison a 24 bit BMP image would occupy 461 KBytes! For comaprison the 480 x 320 Mouse480 image has been to compressed to a mere 6.45 Kbytes! + +When storing the jpeg in a memory array bear in mind the Arduino has a maximum 32767 byte limit for the maximum size of an array (32 KBytes minus 1 byte). + +The decompression of Jpeg images needs more RAM than an UNO provides, thus this library is targetted at processors with more RAM. The library has been tested with Arduino Due and ESP8266/ESP32 based boards. + +The decompression of Jpegs involves a lot of maths, so it takes a Due about ~1.3s to render a fullscreen (480x320 pixel) image and the Mega will take ~5s to do the same. The time for smaller images will reduce roughly pro-rata with the total pixel count. An ESP8266 running at 160MHz and 40MHz SPI coupled to a 320x240 ILI9341 display can render a Jpeg in as little as 240ms. + +This library supports either the SD library (and SdFat for Due). The SdFat allows a bit-bashed SPI interface to an SD Card which can be convenient for example on pins 50, 51 and 52 of a Due (on Mega these are hardware SPI). + +The library has been tested with the 1.8.1 version of the Arduino IDE and may generate error messages at compile time on other versions because "#ifdef \_\_AVR\_\_" is used to distinguish between the Mega and Due and select the correct libraries. + +The library has been tested with 3.2" and 3.0" displays based on the HX8357B, HX8357C and ILI9481 driver chips with a 16 bit parallel interface. Adapting the example sketch for other TFT drivers and their graphics libraries should be quite easy if they support either setWindow() or SetAddrWindow() and pushColor() functions as found in the Adafruit_GFX library. + +On a Mega the number of images stored in FLASH must be limited because it they are large enough to push the executable code start over the 64K 16 bit address limit then the Mega will fail to boot even though the sketch compiles and uploads correctly. This is a limitation imposed by the Arduino environment not this library! The Arduino Mega is not recommended as it does not reliably decode some jpeg images possibly due to a shortage of RAM. The Due will work fine with much bigger image sets in FLASH. + +The ESP8266 and ESP32 has been tested with an ILI9341 library using the SPI interface, with Jpeg images stored in SPIFFS and in Flash arrays. + +SD and SPIFFS filenames can be in String or character array format. File handles can also be used. + +This library has been based on the excellent picojpeg code and the Arduino library port by Makoto Kurauchi here: +https://github.com/MakotoKurauchi/JPEGDecoder + + +Makoto's original Readme below: +============================== + +JPEG Decoder for Arduino + +概要 +---- +Arduino 用 JPEG デコーダです。デコーダ部には [picojpeg](https://code.google.com/p/picojpeg/) を使用しています。 + +サンプルコード +---- +###SerialCsvOut + +SD カード上の JPEG ファイルをブロックごとにデコードし、シリアルから CSV を出力します。 + +変更履歴 +---- +V0.01 - 最初のリリース diff --git a/src/utility/JPEGDecoder/keywords.txt b/src/utility/JPEGDecoder/keywords.txt new file mode 100644 index 0000000..7d0ce47 --- /dev/null +++ b/src/utility/JPEGDecoder/keywords.txt @@ -0,0 +1,35 @@ +####################################### +# Syntax Coloring Map +####################################### +####################################### +# Datatypes (KEYWORD1) +####################################### + +JPEGDecoder KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +JpegDec KEYWORD2 +pImage KEYWORD2 +width KEYWORD2 +height KEYWORD2 +comps KEYWORD2 +MCUSPerRow KEYWORD2 +MCUSPerCol KEYWORD2 +scanType KEYWORD2 +MCUWidth KEYWORD2 +MCUHeight KEYWORD2 +MCUx KEYWORD2 +MCUy KEYWORD2 +JPEGDecoder KEYWORD2 +decode KEYWORD2 +decodeFile KEYWORD2 +decodeSdFile KEYWORD2 +decodeFsFile KEYWORD2 +decodeArray KEYWORD2 +available KEYWORD2 +abort KEYWORD2 +read KEYWORD2 +readSwappedBytes KEYWORD2 diff --git a/src/utility/JPEGDecoder/library.json b/src/utility/JPEGDecoder/library.json new file mode 100644 index 0000000..5729223 --- /dev/null +++ b/src/utility/JPEGDecoder/library.json @@ -0,0 +1,29 @@ +{ + "name": "JPEGDecoder", + "version": "1.7.9", + "keywords": "jpeg, jpg, decoder, TFT", + "description": "A JPEG decoder library, tested on Mega, Due and ESP8266", + "repository": + { + "type": "git", + "url": "https://github.com/Bodmer/JPEGDecoder.git" + }, + "authors": + [ + { + "name": "Bodmer", + "email": "bodmer@anola.net", + "maintainer": true + }, + { + "name": "Makoto Kurauchi", + "url": "http://yushakobo.jp" + }, + { + "name": "Rich Geldreich", + "email": "richgel99@gmail.com" + } + ], + "frameworks": "arduino", + "platforms": ["atmelavr","atmelsam","espressif8266"] +} diff --git a/src/utility/JPEGDecoder/library.properties b/src/utility/JPEGDecoder/library.properties new file mode 100644 index 0000000..a9ed9a7 --- /dev/null +++ b/src/utility/JPEGDecoder/library.properties @@ -0,0 +1,10 @@ +name=JPEGDecoder +version=1.7.9 +author=Bodmer , Makoto Kurauchi, Rich Geldreich +maintainer=Bodmer +sentence= Jpeg decoder tested with Arduino Mega, Arduino Due and ESP8266 based NodeMCU 1.0 +paragraph=Decodes jpeg images stored in arrays, SD card files and SPIFFS files +category=Display +url=https://github.com/Bodmer/JPEGDecoder +architectures=* +includes=JPEGDecoder.h diff --git a/src/utility/JPEGDecoder/src/JPEGDecoder.cpp b/src/utility/JPEGDecoder/src/JPEGDecoder.cpp new file mode 100644 index 0000000..a531a8b --- /dev/null +++ b/src/utility/JPEGDecoder/src/JPEGDecoder.cpp @@ -0,0 +1,423 @@ +/* +JPEGDecoder.cpp + +JPEG Decoder for Arduino +https://github.com/MakotoKurauchi/JPEGDecoder +Public domain, Makoto Kurauchi + +Latest version here: +https://github.com/Bodmer/JPEGDecoder + +Bodmer (21/6/15): Adapted by Bodmer to display JPEGs on TFT (works with Mega and Due) but there +is a memory leak somewhere, crashes after decoding 1 file :-) + +Bodmer (29/1/16): Now in a state with sufficient Mega and Due testing to release in the wild + +Bodmer (various): Various updates and latent bugs fixed + +Bodmer (14/1/17): Tried to merge ESP8266 and SPIFFS support from Frederic Plante's broken branch, + worked on ESP8266, but broke the array handling :-( + +Bodmer (14/1/17): Scrapped all FP's updates, extended the built-in approach to using different + data sources (currently array, SD files and/or SPIFFS files) + +Bodmer (14/1/17): Added ESP8266 support and SPIFFS as a source, added configuration option to + swap bytes to support fast image transfer to TFT using ESP8266 SPI writePattern(). + +Bodmer (15/1/17): Now supports ad hoc use of SPIFFS, SD and arrays without manual configuration. + +Bodmer (19/1/17): Add support for filename being String type + +Bodmer (20/1/17): Correct last mcu block corruption (thanks stevstrong for tracking that bug down!) + +Bodmer (20/1/17): Prevent deleting the pImage pointer twice (causes an exception on ESP8266), + tidy up code. + +Bodmer (24/1/17): Correct greyscale images, update examples +*/ + +#include "JPEGDecoder.h" +#include "picojpeg.h" + +JPEGDecoder JpegDec; + +JPEGDecoder::JPEGDecoder(){ + mcu_x = 0 ; + mcu_y = 0 ; + is_available = 0; + thisPtr = this; +} + + +JPEGDecoder::~JPEGDecoder(){ + if (pImage) delete[] pImage; + pImage = NULL; +} + + +uint8_t JPEGDecoder::pjpeg_callback(uint8_t* pBuf, uint8_t buf_size, uint8_t *pBytes_actually_read, void *pCallback_data) { + JPEGDecoder *thisPtr = JpegDec.thisPtr ; + thisPtr->pjpeg_need_bytes_callback(pBuf, buf_size, pBytes_actually_read, pCallback_data); + return 0; +} + + +uint8_t JPEGDecoder::pjpeg_need_bytes_callback(uint8_t* pBuf, uint8_t buf_size, uint8_t *pBytes_actually_read, void *pCallback_data) { + uint n; + + pCallback_data; + + n = jpg_min(g_nInFileSize - g_nInFileOfs, buf_size); + + if (jpg_source == JPEG_ARRAY) { // We are handling an array + for (int i = 0; i < n; i++) { + pBuf[i] = pgm_read_byte(jpg_data++); + //Serial.println(pBuf[i],HEX); + } + } + +#if defined (LOAD_SD_LIBRARY) || defined (LOAD_SDFAT_LIBRARY) + if (jpg_source == JPEG_SD_FILE) g_pInFileSd.read(pBuf,n); // else we are handling a file +#endif + + *pBytes_actually_read = (uint8_t)(n); + g_nInFileOfs += n; + return 0; +} + +int JPEGDecoder::decode_mcu(void) { + + status = pjpeg_decode_mcu(); + + if (status) { + is_available = 0 ; + + if (status != PJPG_NO_MORE_BLOCKS) { + #ifdef DEBUG + Serial.print("pjpeg_decode_mcu() failed with status "); + Serial.println(status); + #endif + + return -1; + } + } + return 1; +} + + +int JPEGDecoder::read(void) { + int y, x; + uint16_t *pDst_row; + + if(is_available == 0 || mcu_y >= image_info.m_MCUSPerCol) { + abort(); + return 0; + } + + // Copy MCU's pixel blocks into the destination bitmap. + pDst_row = pImage; + for (y = 0; y < image_info.m_MCUHeight; y += 8) { + + const int by_limit = jpg_min(8, image_info.m_height - (mcu_y * image_info.m_MCUHeight + y)); + + for (x = 0; x < image_info.m_MCUWidth; x += 8) { + uint16_t *pDst_block = pDst_row + x; + + // Compute source byte offset of the block in the decoder's MCU buffer. + uint src_ofs = (x * 8U) + (y * 16U); + const uint8_t *pSrcR = image_info.m_pMCUBufR + src_ofs; + const uint8_t *pSrcG = image_info.m_pMCUBufG + src_ofs; + const uint8_t *pSrcB = image_info.m_pMCUBufB + src_ofs; + + const int bx_limit = jpg_min(8, image_info.m_width - (mcu_x * image_info.m_MCUWidth + x)); + + if (image_info.m_scanType == PJPG_GRAYSCALE) { + int bx, by; + for (by = 0; by < by_limit; by++) { + uint16_t *pDst = pDst_block; + + for (bx = 0; bx < bx_limit; bx++) { +#ifdef SWAP_BYTES + *pDst++ = (*pSrcR & 0xF8) | (*pSrcR & 0xE0) >> 5 | (*pSrcR & 0xF8) << 5 | (*pSrcR & 0x1C) << 11; +#else + *pDst++ = (*pSrcR & 0xF8) << 8 | (*pSrcR & 0xFC) <<3 | *pSrcR >> 3; +#endif + pSrcR++; + } + + pSrcR += (8 - bx_limit); + + pDst_block += row_pitch; + } + } + else { + int bx, by; + for (by = 0; by < by_limit; by++) { + uint16_t *pDst = pDst_block; + + for (bx = 0; bx < bx_limit; bx++) { +#ifdef SWAP_BYTES + *pDst++ = (*pSrcR & 0xF8) | (*pSrcG & 0xE0) >> 5 | (*pSrcB & 0xF8) << 5 | (*pSrcG & 0x1C) << 11; +#else + *pDst++ = (*pSrcR & 0xF8) << 8 | (*pSrcG & 0xFC) <<3 | *pSrcB >> 3; +#endif + pSrcR++; pSrcG++; pSrcB++; + } + + pSrcR += (8 - bx_limit); + pSrcG += (8 - bx_limit); + pSrcB += (8 - bx_limit); + + pDst_block += row_pitch; + } + } + } + pDst_row += (row_pitch * 8); + } + + MCUx = mcu_x; + MCUy = mcu_y; + + mcu_x++; + if (mcu_x == image_info.m_MCUSPerRow) { + mcu_x = 0; + mcu_y++; + } + + if(decode_mcu()==-1) is_available = 0 ; + + return 1; +} + +int JPEGDecoder::readSwappedBytes(void) { + int y, x; + uint16_t *pDst_row; + + if(is_available == 0 || mcu_y >= image_info.m_MCUSPerCol) { + abort(); + return 0; + } + + // Copy MCU's pixel blocks into the destination bitmap. + pDst_row = pImage; + for (y = 0; y < image_info.m_MCUHeight; y += 8) { + + const int by_limit = jpg_min(8, image_info.m_height - (mcu_y * image_info.m_MCUHeight + y)); + + for (x = 0; x < image_info.m_MCUWidth; x += 8) { + uint16_t *pDst_block = pDst_row + x; + + // Compute source byte offset of the block in the decoder's MCU buffer. + uint src_ofs = (x * 8U) + (y * 16U); + const uint8_t *pSrcR = image_info.m_pMCUBufR + src_ofs; + const uint8_t *pSrcG = image_info.m_pMCUBufG + src_ofs; + const uint8_t *pSrcB = image_info.m_pMCUBufB + src_ofs; + + const int bx_limit = jpg_min(8, image_info.m_width - (mcu_x * image_info.m_MCUWidth + x)); + + if (image_info.m_scanType == PJPG_GRAYSCALE) { + int bx, by; + for (by = 0; by < by_limit; by++) { + uint16_t *pDst = pDst_block; + + for (bx = 0; bx < bx_limit; bx++) { + + *pDst++ = (*pSrcR & 0xF8) | (*pSrcR & 0xE0) >> 5 | (*pSrcR & 0xF8) << 5 | (*pSrcR & 0x1C) << 11; + + pSrcR++; + } + } + } + else { + int bx, by; + for (by = 0; by < by_limit; by++) { + uint16_t *pDst = pDst_block; + + for (bx = 0; bx < bx_limit; bx++) { + + *pDst++ = (*pSrcR & 0xF8) | (*pSrcG & 0xE0) >> 5 | (*pSrcB & 0xF8) << 5 | (*pSrcG & 0x1C) << 11; + + pSrcR++; pSrcG++; pSrcB++; + } + + pSrcR += (8 - bx_limit); + pSrcG += (8 - bx_limit); + pSrcB += (8 - bx_limit); + + pDst_block += row_pitch; + } + } + } + pDst_row += (row_pitch * 8); + } + + MCUx = mcu_x; + MCUy = mcu_y; + + mcu_x++; + if (mcu_x == image_info.m_MCUSPerRow) { + mcu_x = 0; + mcu_y++; + } + + if(decode_mcu()==-1) is_available = 0 ; + + return 1; +} + + +// Generic file call for SD or SPIFFS, uses leading / to distinguish SPIFFS files +int JPEGDecoder::decodeFile(const char *pFilename){ + +#if defined (ESP8266) || defined (ESP32) +#if defined (LOAD_SD_LIBRARY) || defined (LOAD_SDFAT_LIBRARY) + if (*pFilename == '/') +#endif + return decodeFsFile(pFilename); +#endif + +#if defined (LOAD_SD_LIBRARY) || defined (LOAD_SDFAT_LIBRARY) + return decodeSdFile(pFilename); +#endif + + return -1; +} + +int JPEGDecoder::decodeFile(const String& pFilename){ + +#if defined (ESP8266) || defined (ESP32) +#if defined (LOAD_SD_LIBRARY) || defined (LOAD_SDFAT_LIBRARY) + if (pFilename.charAt(0) == '/') +#endif + return decodeFsFile(pFilename); +#endif + +#if defined (LOAD_SD_LIBRARY) || defined (LOAD_SDFAT_LIBRARY) + return decodeSdFile(pFilename); +#endif + + return -1; +} + + +#if defined (LOAD_SD_LIBRARY) || defined (LOAD_SDFAT_LIBRARY) + +// Call specific to SD filing system in case leading / is used +int JPEGDecoder::decodeSdFile(const char *pFilename) { + + File pInFile = SD.open( pFilename, FILE_READ); + + return decodeSdFile(pInFile); +} + + +int JPEGDecoder::decodeSdFile(const String& pFilename) { +#if !defined (ARDUINO_ARCH_SAM) + File pInFile = SD.open( pFilename, FILE_READ); + + return decodeSdFile(pInFile); +#else + return -1; +#endif +} + + +int JPEGDecoder::decodeSdFile(File jpgFile) { // This is for the SD library + + g_pInFileSd = jpgFile; + + jpg_source = JPEG_SD_FILE; // Flag to indicate a SD file + + if (!g_pInFileSd) { + #ifdef DEBUG + Serial.println("ERROR: SD file not found!"); + #endif + + return -1; + } + + g_nInFileOfs = 0; + + g_nInFileSize = g_pInFileSd.size(); + + return decodeCommon(); + +} +#endif + + +int JPEGDecoder::decodeArray(const uint8_t array[], uint32_t array_size) { + + jpg_source = JPEG_ARRAY; // We are not processing a file, use arrays + + g_nInFileOfs = 0; + + jpg_data = (uint8_t *)array; + + g_nInFileSize = array_size; + + return decodeCommon(); +} + + +int JPEGDecoder::decodeCommon(void) { + + width = 0; + height = 0; + comps = 0; + MCUSPerRow = 0; + MCUSPerCol = 0; + scanType = (pjpeg_scan_type_t)0; + MCUWidth = 0; + MCUHeight = 0; + + status = pjpeg_decode_init(&image_info, pjpeg_callback, NULL, 0); + + if (status) { + Serial.print("pjpeg_decode_init() failed with status "); + Serial.println(status); + + if (status == PJPG_UNSUPPORTED_MODE) { + Serial.println("Progressive JPEG files are not supported."); + } + + return 0; + } + + decoded_width = image_info.m_width; + decoded_height = image_info.m_height; + + row_pitch = image_info.m_MCUWidth; + pImage = new uint16_t[image_info.m_MCUWidth * image_info.m_MCUHeight]; + + memset(pImage , 0 , image_info.m_MCUWidth * image_info.m_MCUHeight * sizeof(*pImage)); + + row_blocks_per_mcu = image_info.m_MCUWidth >> 3; + col_blocks_per_mcu = image_info.m_MCUHeight >> 3; + + is_available = 1 ; + + width = decoded_width; + height = decoded_height; + comps = 1; + MCUSPerRow = image_info.m_MCUSPerRow; + MCUSPerCol = image_info.m_MCUSPerCol; + scanType = image_info.m_scanType; + MCUWidth = image_info.m_MCUWidth; + MCUHeight = image_info.m_MCUHeight; + + return decode_mcu(); +} + +void JPEGDecoder::abort(void) { + + mcu_x = 0 ; + mcu_y = 0 ; + is_available = 0; + if(pImage) delete[] pImage; + pImage = NULL; + +#if defined (LOAD_SD_LIBRARY) || defined (LOAD_SDFAT_LIBRARY) + if (jpg_source == JPEG_SD_FILE) if (g_pInFileSd) g_pInFileSd.close(); +#endif +} diff --git a/src/utility/JPEGDecoder/src/JPEGDecoder.h b/src/utility/JPEGDecoder/src/JPEGDecoder.h new file mode 100644 index 0000000..ce68227 --- /dev/null +++ b/src/utility/JPEGDecoder/src/JPEGDecoder.h @@ -0,0 +1,149 @@ +/* +JPEGDecoder.h + +JPEG Decoder for Arduino +Public domain, Makoto Kurauchi + +Adapted by Bodmer for use with a TFT screen + +Latest version here: +https://github.com/Bodmer/JPEGDecoder + +*/ +#define LOAD_SDFAT_LIBRARY +#ifndef JPEGDECODER_H + #define JPEGDECODER_H + + #include "User_Config.h" + + #include "Arduino.h" + + #if defined (ESP8266) || defined (ESP32) + + //#include "arduino.h" + #include + +// If the sketch has included FS.h without setting FS_NO_GLOBALS first then it is likely +// there will be a redefinition of 'class fs::File' error due to conflict with the +// SD library, so we can't load the SD library. (At 12/1/18 this appears to be fixed) + //#if !defined (FS_NO_GLOBALS) && defined (FS_H) + //#undef LOAD_SD_LIBRARY + //#undef LOAD_SDFAT_LIBRARY + //#endif + + #ifdef ESP32 // SDFAT library not compatible with ESP32 + //#undef LOAD_SD_LIBRARY + #undef LOAD_SDFAT_LIBRARY + #endif + + #define LOAD_SPIFFS + #define FS_NO_GLOBALS + + #ifdef ESP32 + #endif + + #endif + + #if defined (LOAD_SD_LIBRARY) || defined (LOAD_SDFAT_LIBRARY) + #ifdef LOAD_SDFAT_LIBRARY + #else + #endif + #endif + +#include "../../SdFat.h" // Alternative where we might need to bit bash the SPI + + +#include "picojpeg.h" + +enum { + JPEG_ARRAY = 0, + JPEG_FS_FILE, + JPEG_SD_FILE +}; + +//#define DEBUG + +//------------------------------------------------------------------------------ +#ifndef jpg_min + #define jpg_min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef min + #define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +//------------------------------------------------------------------------------ +typedef unsigned char uint8; +typedef unsigned int uint; +//------------------------------------------------------------------------------ + +class JPEGDecoder { + +private: + SdFat SD; + File g_pInFileSd; + pjpeg_scan_type_t scan_type; + pjpeg_image_info_t image_info; + + int is_available; + int mcu_x; + int mcu_y; + uint g_nInFileSize; + uint g_nInFileOfs; + uint row_pitch; + uint decoded_width, decoded_height; + uint row_blocks_per_mcu, col_blocks_per_mcu; + uint8 status; + uint8 jpg_source = 0; + uint8_t *jpg_data; + + static uint8 pjpeg_callback(unsigned char *pBuf, unsigned char buf_size, unsigned char *pBytes_actually_read, void *pCallback_data); + uint8 pjpeg_need_bytes_callback(unsigned char *pBuf, unsigned char buf_size, unsigned char *pBytes_actually_read, void *pCallback_data); + int decode_mcu(void); + int decodeCommon(void); +public: + + uint16_t *pImage; + JPEGDecoder *thisPtr; + + int width; + int height; + int comps; + int MCUSPerRow; + int MCUSPerCol; + pjpeg_scan_type_t scanType; + int MCUWidth; + int MCUHeight; + int MCUx; + int MCUy; + + JPEGDecoder(); + ~JPEGDecoder(); + + int available(void); + int read(void); + int readSwappedBytes(void); + + int decodeFile (const char *pFilename); + int decodeFile (const String& pFilename); + +#if defined (LOAD_SD_LIBRARY) || defined (LOAD_SDFAT_LIBRARY) + int decodeSdFile (const char *pFilename); + int decodeSdFile (const String& pFilename); + int decodeSdFile (File g_pInFile); +#endif + +#ifdef LOAD_SPIFFS + int decodeFsFile (const char *pFilename); + int decodeFsFile (const String& pFilename); + int decodeFsFile (File g_pInFile); +#endif + + int decodeArray(const uint8_t array[], uint32_t array_size); + void abort(void); + +}; + +extern JPEGDecoder JpegDec; + +#endif // JPEGDECODER_H diff --git a/src/utility/JPEGDecoder/src/User_Config.h b/src/utility/JPEGDecoder/src/User_Config.h new file mode 100644 index 0000000..4bce9aa --- /dev/null +++ b/src/utility/JPEGDecoder/src/User_Config.h @@ -0,0 +1,42 @@ +// Comment out the next #defines if you are not using an SD Card to store the JPEGs +// Commenting out the line is NOT essential but will save some FLASH space if +// SD Card access is not needed. Note: use of SdFat is currently untested! + +#define LOAD_SD_LIBRARY // Default SD Card library +//#define LOAD_SDFAT_LIBRARY // Use SdFat library instead, so SD Card SPI can be bit bashed + + +// Note for ESP8266 users: +// If the sketch uses SPIFFS and has included FS.h without defining FS_NO_GLOBALS first +// then the JPEGDecoder library will NOT load the SD or SdFat libraries. Use lines thus +// in your sketch (see the examples included in the JPEGDecoder library): +/* +#define FS_NO_GLOBALS +#include + +// You will then need to directly reference the SPIFFS File type thus in the sketch, e.g.: + +fs::File jpegFile = SPIFFS.open( filename, "r"); // Example + +// This will then allow the default method of using the SD library File type to be used +// in the same sketch, e.g.: + +File jpegFile = SD.open( filename, FILE_READ); + +*/ + +// This is all to avoid a redefinition of 'class fs::File' error due to a conflict between the +// duplicate definitions in the SD library and the SPIFFS library. + + +#ifdef ESP8266 + // Uncomment out the next #define if you want the bytes swapped in the image blocks + // returned by read(). + + // Swapping the bytes is only needed to use the ESP8266 SPI library writePattern() + // member function and it is better to use readSwappedBytes() instead of read() in + // the sketch. Images will look psychedelic with wrong colours if the SPI transmit byte + // order is not right for your sketch! + + // #define SWAP_BYTES // Deprecated, only included for backwards compatibility +#endif diff --git a/src/utility/JPEGDecoder/src/picojpeg.c b/src/utility/JPEGDecoder/src/picojpeg.c new file mode 100644 index 0000000..6fc1ae5 --- /dev/null +++ b/src/utility/JPEGDecoder/src/picojpeg.c @@ -0,0 +1,2320 @@ +//------------------------------------------------------------------------------ +// picojpeg.c v1.1 - Public domain, Rich Geldreich +// Nov. 27, 2010 - Initial release +// Feb. 9, 2013 - Added H1V2/H2V1 support, cleaned up macros, signed shift fixes +// Also integrated and tested changes from Chris Phoenix . +//------------------------------------------------------------------------------ +#include "picojpeg.h" +//------------------------------------------------------------------------------ +// Set to 1 if right shifts on signed ints are always unsigned (logical) shifts +// When 1, arithmetic right shifts will be emulated by using a logical shift +// with special case code to ensure the sign bit is replicated. +#define PJPG_RIGHT_SHIFT_IS_ALWAYS_UNSIGNED 1 + +// Define PJPG_INLINE to "inline" if your C compiler supports explicit inlining +#define PJPG_INLINE +//------------------------------------------------------------------------------ +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef signed char int8; +typedef signed short int16; +//------------------------------------------------------------------------------ +#if PJPG_RIGHT_SHIFT_IS_ALWAYS_UNSIGNED +static int16 replicateSignBit16(int8 n) +{ + switch (n) + { + case 0: return 0x0000; + case 1: return 0x8000; + case 2: return 0xC000; + case 3: return 0xE000; + case 4: return 0xF000; + case 5: return 0xF800; + case 6: return 0xFC00; + case 7: return 0xFE00; + case 8: return 0xFF00; + case 9: return 0xFF80; + case 10: return 0xFFC0; + case 11: return 0xFFE0; + case 12: return 0xFFF0; + case 13: return 0xFFF8; + case 14: return 0xFFFC; + case 15: return 0xFFFE; + default: return 0xFFFF; + } +} +static PJPG_INLINE int16 arithmeticRightShiftN16(int16 x, int8 n) +{ + int16 r = (uint16)x >> (uint8)n; + if (x < 0) + r |= replicateSignBit16(n); + return r; +} +static PJPG_INLINE long arithmeticRightShift8L(long x) +{ + long r = (unsigned long)x >> 8U; + if (x < 0) + r |= ~(~(unsigned long)0U >> 8U); + return r; +} +#define PJPG_ARITH_SHIFT_RIGHT_N_16(x, n) arithmeticRightShiftN16(x, n) +#define PJPG_ARITH_SHIFT_RIGHT_8_L(x) arithmeticRightShift8L(x) +#else +#define PJPG_ARITH_SHIFT_RIGHT_N_16(x, n) ((x) >> (n)) +#define PJPG_ARITH_SHIFT_RIGHT_8_L(x) ((x) >> 8) +#endif +//------------------------------------------------------------------------------ +// Change as needed - the PJPG_MAX_WIDTH/PJPG_MAX_HEIGHT checks are only present +// to quickly detect bogus files. +#define PJPG_MAX_WIDTH 16384 +#define PJPG_MAX_HEIGHT 16384 +#define PJPG_MAXCOMPSINSCAN 3 +//------------------------------------------------------------------------------ +typedef enum +{ + M_SOF0 = 0xC0, + M_SOF1 = 0xC1, + M_SOF2 = 0xC2, + M_SOF3 = 0xC3, + + M_SOF5 = 0xC5, + M_SOF6 = 0xC6, + M_SOF7 = 0xC7, + + M_JPG = 0xC8, + M_SOF9 = 0xC9, + M_SOF10 = 0xCA, + M_SOF11 = 0xCB, + + M_SOF13 = 0xCD, + M_SOF14 = 0xCE, + M_SOF15 = 0xCF, + + M_DHT = 0xC4, + + M_DAC = 0xCC, + + M_RST0 = 0xD0, + M_RST1 = 0xD1, + M_RST2 = 0xD2, + M_RST3 = 0xD3, + M_RST4 = 0xD4, + M_RST5 = 0xD5, + M_RST6 = 0xD6, + M_RST7 = 0xD7, + + M_SOI = 0xD8, + M_EOI = 0xD9, + M_SOS = 0xDA, + M_DQT = 0xDB, + M_DNL = 0xDC, + M_DRI = 0xDD, + M_DHP = 0xDE, + M_EXP = 0xDF, + + M_APP0 = 0xE0, + M_APP15 = 0xEF, + + M_JPG0 = 0xF0, + M_JPG13 = 0xFD, + M_COM = 0xFE, + + M_TEM = 0x01, + + M_ERROR = 0x100, + + RST0 = 0xD0 +} JPEG_MARKER; +//------------------------------------------------------------------------------ +static const int8 ZAG[] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, +}; +//------------------------------------------------------------------------------ +// 128 bytes +static int16 gCoeffBuf[8*8]; + +// 8*8*4 bytes * 3 = 768 +static uint8 gMCUBufR[256]; +static uint8 gMCUBufG[256]; +static uint8 gMCUBufB[256]; + +// 256 bytes +static int16 gQuant0[8*8]; +static int16 gQuant1[8*8]; + +// 6 bytes +static int16 gLastDC[3]; + +typedef struct HuffTableT +{ + uint16 mMinCode[16]; + uint16 mMaxCode[16]; + uint8 mValPtr[16]; +} HuffTable; + +// DC - 192 +static HuffTable gHuffTab0; + +static uint8 gHuffVal0[16]; + +static HuffTable gHuffTab1; +static uint8 gHuffVal1[16]; + +// AC - 672 +static HuffTable gHuffTab2; +static uint8 gHuffVal2[256]; + +static HuffTable gHuffTab3; +static uint8 gHuffVal3[256]; + +static uint8 gValidHuffTables; +static uint8 gValidQuantTables; + +static uint8 gTemFlag; +#define PJPG_MAX_IN_BUF_SIZE 256 +static uint8 gInBuf[PJPG_MAX_IN_BUF_SIZE]; +static uint8 gInBufOfs; +static uint8 gInBufLeft; + +static uint16 gBitBuf; +static uint8 gBitsLeft; +//------------------------------------------------------------------------------ +static uint16 gImageXSize; +static uint16 gImageYSize; +static uint8 gCompsInFrame; +static uint8 gCompIdent[3]; +static uint8 gCompHSamp[3]; +static uint8 gCompVSamp[3]; +static uint8 gCompQuant[3]; + +static uint16 gRestartInterval; +static uint16 gNextRestartNum; +static uint16 gRestartsLeft; + +static uint8 gCompsInScan; +static uint8 gCompList[3]; +static uint8 gCompDCTab[3]; // 0,1 +static uint8 gCompACTab[3]; // 0,1 + +static pjpeg_scan_type_t gScanType; + +static uint8 gMaxBlocksPerMCU; +static uint8 gMaxMCUXSize; +static uint8 gMaxMCUYSize; +static uint16 gMaxMCUSPerRow; +static uint16 gMaxMCUSPerCol; +static uint16 gNumMCUSRemaining; +static uint8 gMCUOrg[6]; + +static pjpeg_need_bytes_callback_t g_pNeedBytesCallback; +static void *g_pCallback_data; +static uint8 gCallbackStatus; +static uint8 gReduce; +//------------------------------------------------------------------------------ +static void fillInBuf(void) +{ + unsigned char status; + + // Reserve a few bytes at the beginning of the buffer for putting back ("stuffing") chars. + gInBufOfs = 4; + gInBufLeft = 0; + + status = (*g_pNeedBytesCallback)(gInBuf + gInBufOfs, PJPG_MAX_IN_BUF_SIZE - gInBufOfs, &gInBufLeft, g_pCallback_data); + if (status) + { + // The user provided need bytes callback has indicated an error, so record the error and continue trying to decode. + // The highest level pjpeg entrypoints will catch the error and return the non-zero status. + gCallbackStatus = status; + } +} +//------------------------------------------------------------------------------ +static PJPG_INLINE uint8 getChar(void) +{ + if (!gInBufLeft) + { + fillInBuf(); + if (!gInBufLeft) + { + gTemFlag = ~gTemFlag; + return gTemFlag ? 0xFF : 0xD9; + } + } + + gInBufLeft--; + return gInBuf[gInBufOfs++]; +} +//------------------------------------------------------------------------------ +static PJPG_INLINE void stuffChar(uint8 i) +{ + gInBufOfs--; + gInBuf[gInBufOfs] = i; + gInBufLeft++; +} +//------------------------------------------------------------------------------ +static PJPG_INLINE uint8 getOctet(uint8 FFCheck) +{ + uint8 c = getChar(); + + if ((FFCheck) && (c == 0xFF)) + { + uint8 n = getChar(); + + if (n) + { + stuffChar(n); + stuffChar(0xFF); + } + } + + return c; +} +//------------------------------------------------------------------------------ +static uint16 getBits(uint8 numBits, uint8 FFCheck) +{ + uint8 origBits = numBits; + uint16 ret = gBitBuf; + + if (numBits > 8) + { + numBits -= 8; + + gBitBuf <<= gBitsLeft; + + gBitBuf |= getOctet(FFCheck); + + gBitBuf <<= (8 - gBitsLeft); + + ret = (ret & 0xFF00) | (gBitBuf >> 8); + } + + if (gBitsLeft < numBits) + { + gBitBuf <<= gBitsLeft; + + gBitBuf |= getOctet(FFCheck); + + gBitBuf <<= (numBits - gBitsLeft); + + gBitsLeft = 8 - (numBits - gBitsLeft); + } + else + { + gBitsLeft = (uint8)(gBitsLeft - numBits); + gBitBuf <<= numBits; + } + + return ret >> (16 - origBits); +} +//------------------------------------------------------------------------------ +static PJPG_INLINE uint16 getBits1(uint8 numBits) +{ + return getBits(numBits, 0); +} +//------------------------------------------------------------------------------ +static PJPG_INLINE uint16 getBits2(uint8 numBits) +{ + return getBits(numBits, 1); +} +//------------------------------------------------------------------------------ +static PJPG_INLINE uint8 getBit(void) +{ + uint8 ret = 0; + if (gBitBuf & 0x8000) + ret = 1; + + if (!gBitsLeft) + { + gBitBuf |= getOctet(1); + + gBitsLeft += 8; + } + + gBitsLeft--; + gBitBuf <<= 1; + + return ret; +} +//------------------------------------------------------------------------------ +static uint16 getExtendTest(uint8 i) +{ + switch (i) + { + case 0: return 0; + case 1: return 0x0001; + case 2: return 0x0002; + case 3: return 0x0004; + case 4: return 0x0008; + case 5: return 0x0010; + case 6: return 0x0020; + case 7: return 0x0040; + case 8: return 0x0080; + case 9: return 0x0100; + case 10: return 0x0200; + case 11: return 0x0400; + case 12: return 0x0800; + case 13: return 0x1000; + case 14: return 0x2000; + case 15: return 0x4000; + default: return 0; + } +} +//------------------------------------------------------------------------------ +static int16 getExtendOffset(uint8 i) +{ + switch (i) + { + case 0: return 0; + case 1: return ((-1)<<1) + 1; + case 2: return ((-1)<<2) + 1; + case 3: return ((-1)<<3) + 1; + case 4: return ((-1)<<4) + 1; + case 5: return ((-1)<<5) + 1; + case 6: return ((-1)<<6) + 1; + case 7: return ((-1)<<7) + 1; + case 8: return ((-1)<<8) + 1; + case 9: return ((-1)<<9) + 1; + case 10: return ((-1)<<10) + 1; + case 11: return ((-1)<<11) + 1; + case 12: return ((-1)<<12) + 1; + case 13: return ((-1)<<13) + 1; + case 14: return ((-1)<<14) + 1; + case 15: return ((-1)<<15) + 1; + default: return 0; + } +}; +//------------------------------------------------------------------------------ +static PJPG_INLINE int16 huffExtend(uint16 x, uint8 s) +{ + return ((x < getExtendTest(s)) ? ((int16)x + getExtendOffset(s)) : (int16)x); +} +//------------------------------------------------------------------------------ +static PJPG_INLINE uint8 huffDecode(const HuffTable* pHuffTable, const uint8* pHuffVal) +{ + uint8 i = 0; + uint8 j; + uint16 code = getBit(); + + // This func only reads a bit at a time, which on modern CPU's is not terribly efficient. + // But on microcontrollers without strong integer shifting support this seems like a + // more reasonable approach. + for ( ; ; ) + { + uint16 maxCode; + + if (i == 16) + return 0; + + maxCode = pHuffTable->mMaxCode[i]; + if ((code <= maxCode) && (maxCode != 0xFFFF)) + break; + + i++; + code <<= 1; + code |= getBit(); + } + + j = pHuffTable->mValPtr[i]; + j = (uint8)(j + (code - pHuffTable->mMinCode[i])); + + return pHuffVal[j]; +} +//------------------------------------------------------------------------------ +static void huffCreate(const uint8* pBits, HuffTable* pHuffTable) +{ + uint8 i = 0; + uint8 j = 0; + + uint16 code = 0; + + for ( ; ; ) + { + uint8 num = pBits[i]; + + if (!num) + { + pHuffTable->mMinCode[i] = 0x0000; + pHuffTable->mMaxCode[i] = 0xFFFF; + pHuffTable->mValPtr[i] = 0; + } + else + { + pHuffTable->mMinCode[i] = code; + pHuffTable->mMaxCode[i] = code + num - 1; + pHuffTable->mValPtr[i] = j; + + j = (uint8)(j + num); + + code = (uint16)(code + num); + } + + code <<= 1; + + i++; + if (i > 15) + break; + } +} +//------------------------------------------------------------------------------ +static HuffTable* getHuffTable(uint8 index) +{ + // 0-1 = DC + // 2-3 = AC + switch (index) + { + case 0: return &gHuffTab0; + case 1: return &gHuffTab1; + case 2: return &gHuffTab2; + case 3: return &gHuffTab3; + default: return 0; + } +} +//------------------------------------------------------------------------------ +static uint8* getHuffVal(uint8 index) +{ + // 0-1 = DC + // 2-3 = AC + switch (index) + { + case 0: return gHuffVal0; + case 1: return gHuffVal1; + case 2: return gHuffVal2; + case 3: return gHuffVal3; + default: return 0; + } +} +//------------------------------------------------------------------------------ +static uint16 getMaxHuffCodes(uint8 index) +{ + return (index < 2) ? 12 : 255; +} +//------------------------------------------------------------------------------ +static uint8 readDHTMarker(void) +{ + uint8 bits[16]; + uint16 left = getBits1(16); + + if (left < 2) + return PJPG_BAD_DHT_MARKER; + + left -= 2; + + while (left) + { + uint8 i, tableIndex, index; + uint8* pHuffVal; + HuffTable* pHuffTable; + uint16 count, totalRead; + + index = (uint8)getBits1(8); + + if ( ((index & 0xF) > 1) || ((index & 0xF0) > 0x10) ) + return PJPG_BAD_DHT_INDEX; + + tableIndex = ((index >> 3) & 2) + (index & 1); + + pHuffTable = getHuffTable(tableIndex); + pHuffVal = getHuffVal(tableIndex); + + gValidHuffTables |= (1 << tableIndex); + + count = 0; + for (i = 0; i <= 15; i++) + { + uint8 n = (uint8)getBits1(8); + bits[i] = n; + count = (uint16)(count + n); + } + + if (count > getMaxHuffCodes(tableIndex)) + return PJPG_BAD_DHT_COUNTS; + + for (i = 0; i < count; i++) + pHuffVal[i] = (uint8)getBits1(8); + + totalRead = 1 + 16 + count; + + if (left < totalRead) + return PJPG_BAD_DHT_MARKER; + + left = (uint16)(left - totalRead); + + huffCreate(bits, pHuffTable); + } + + return 0; +} +//------------------------------------------------------------------------------ +static void createWinogradQuant(int16* pQuant); + +static uint8 readDQTMarker(void) +{ + uint16 left = getBits1(16); + + if (left < 2) + return PJPG_BAD_DQT_MARKER; + + left -= 2; + + while (left) + { + uint8 i; + uint8 n = (uint8)getBits1(8); + uint8 prec = n >> 4; + uint16 totalRead; + + n &= 0x0F; + + if (n > 1) + return PJPG_BAD_DQT_TABLE; + + gValidQuantTables |= (n ? 2 : 1); + + // read quantization entries, in zag order + for (i = 0; i < 64; i++) + { + uint16 temp = getBits1(8); + + if (prec) + temp = (temp << 8) + getBits1(8); + + if (n) + gQuant1[i] = (int16)temp; + else + gQuant0[i] = (int16)temp; + } + + createWinogradQuant(n ? gQuant1 : gQuant0); + + totalRead = 64 + 1; + + if (prec) + totalRead += 64; + + if (left < totalRead) + return PJPG_BAD_DQT_LENGTH; + + left = (uint16)(left - totalRead); + } + + return 0; +} +//------------------------------------------------------------------------------ +static uint8 readSOFMarker(void) +{ + uint8 i; + uint16 left = getBits1(16); + + if (getBits1(8) != 8) + return PJPG_BAD_PRECISION; + + gImageYSize = getBits1(16); + + if ((!gImageYSize) || (gImageYSize > PJPG_MAX_HEIGHT)) + return PJPG_BAD_HEIGHT; + + gImageXSize = getBits1(16); + + if ((!gImageXSize) || (gImageXSize > PJPG_MAX_WIDTH)) + return PJPG_BAD_WIDTH; + + gCompsInFrame = (uint8)getBits1(8); + + if (gCompsInFrame > 3) + return PJPG_TOO_MANY_COMPONENTS; + + if (left != (gCompsInFrame + gCompsInFrame + gCompsInFrame + 8)) + return PJPG_BAD_SOF_LENGTH; + + for (i = 0; i < gCompsInFrame; i++) + { + gCompIdent[i] = (uint8)getBits1(8); + gCompHSamp[i] = (uint8)getBits1(4); + gCompVSamp[i] = (uint8)getBits1(4); + gCompQuant[i] = (uint8)getBits1(8); + + if (gCompQuant[i] > 1) + return PJPG_UNSUPPORTED_QUANT_TABLE; + } + + return 0; +} +//------------------------------------------------------------------------------ +// Used to skip unrecognized markers. +static uint8 skipVariableMarker(void) +{ + uint16 left = getBits1(16); + + if (left < 2) + return PJPG_BAD_VARIABLE_MARKER; + + left -= 2; + + while (left) + { + getBits1(8); + left--; + } + + return 0; +} +//------------------------------------------------------------------------------ +// Read a define restart interval (DRI) marker. +static uint8 readDRIMarker(void) +{ + if (getBits1(16) != 4) + return PJPG_BAD_DRI_LENGTH; + + gRestartInterval = getBits1(16); + + return 0; +} +//------------------------------------------------------------------------------ +// Read a start of scan (SOS) marker. +static uint8 readSOSMarker(void) +{ + uint8 i; + uint16 left = getBits1(16); + uint8 spectral_start, spectral_end, successive_high, successive_low; + + gCompsInScan = (uint8)getBits1(8); + + left -= 3; + + if ( (left != (gCompsInScan + gCompsInScan + 3)) || (gCompsInScan < 1) || (gCompsInScan > PJPG_MAXCOMPSINSCAN) ) + return PJPG_BAD_SOS_LENGTH; + + for (i = 0; i < gCompsInScan; i++) + { + uint8 cc = (uint8)getBits1(8); + uint8 c = (uint8)getBits1(8); + uint8 ci; + + left -= 2; + + for (ci = 0; ci < gCompsInFrame; ci++) + if (cc == gCompIdent[ci]) + break; + + if (ci >= gCompsInFrame) + return PJPG_BAD_SOS_COMP_ID; + + gCompList[i] = ci; + gCompDCTab[ci] = (c >> 4) & 15; + gCompACTab[ci] = (c & 15); + } + + spectral_start = (uint8)getBits1(8); + spectral_end = (uint8)getBits1(8); + successive_high = (uint8)getBits1(4); + successive_low = (uint8)getBits1(4); + + left -= 3; + + while (left) + { + getBits1(8); + left--; + } + + return 0; +} +//------------------------------------------------------------------------------ +static uint8 nextMarker(void) +{ + uint8 c; + uint8 bytes = 0; + + do + { + do + { + bytes++; + + c = (uint8)getBits1(8); + + } while (c != 0xFF); + + do + { + c = (uint8)getBits1(8); + + } while (c == 0xFF); + + } while (c == 0); + + // If bytes > 0 here, there where extra bytes before the marker (not good). + + return c; +} +//------------------------------------------------------------------------------ +// Process markers. Returns when an SOFx, SOI, EOI, or SOS marker is +// encountered. +static uint8 processMarkers(uint8* pMarker) +{ + for ( ; ; ) + { + uint8 c = nextMarker(); + + switch (c) + { + case M_SOF0: + case M_SOF1: + case M_SOF2: + case M_SOF3: + case M_SOF5: + case M_SOF6: + case M_SOF7: + // case M_JPG: + case M_SOF9: + case M_SOF10: + case M_SOF11: + case M_SOF13: + case M_SOF14: + case M_SOF15: + case M_SOI: + case M_EOI: + case M_SOS: + { + *pMarker = c; + return 0; + } + case M_DHT: + { + readDHTMarker(); + break; + } + // Sorry, no arithmetic support at this time. Dumb patents! + case M_DAC: + { + return PJPG_NO_ARITHMITIC_SUPPORT; + } + case M_DQT: + { + readDQTMarker(); + break; + } + case M_DRI: + { + readDRIMarker(); + break; + } + //case M_APP0: /* no need to read the JFIF marker */ + + case M_JPG: + case M_RST0: /* no parameters */ + case M_RST1: + case M_RST2: + case M_RST3: + case M_RST4: + case M_RST5: + case M_RST6: + case M_RST7: + case M_TEM: + { + return PJPG_UNEXPECTED_MARKER; + } + default: /* must be DNL, DHP, EXP, APPn, JPGn, COM, or RESn or APP0 */ + { + skipVariableMarker(); + break; + } + } + } +// return 0; +} +//------------------------------------------------------------------------------ +// Finds the start of image (SOI) marker. +static uint8 locateSOIMarker(void) +{ + uint16 bytesleft; + + uint8 lastchar = (uint8)getBits1(8); + + uint8 thischar = (uint8)getBits1(8); + + /* ok if it's a normal JPEG file without a special header */ + + if ((lastchar == 0xFF) && (thischar == M_SOI)) + return 0; + + bytesleft = 4096; //512; + + for ( ; ; ) + { + if (--bytesleft == 0) + return PJPG_NOT_JPEG; + + lastchar = thischar; + + thischar = (uint8)getBits1(8); + + if (lastchar == 0xFF) + { + if (thischar == M_SOI) + break; + else if (thischar == M_EOI) //getBits1 will keep returning M_EOI if we read past the end + return PJPG_NOT_JPEG; + } + } + + /* Check the next character after marker: if it's not 0xFF, it can't + be the start of the next marker, so the file is bad */ + + thischar = (uint8)((gBitBuf >> 8) & 0xFF); + + if (thischar != 0xFF) + return PJPG_NOT_JPEG; + + return 0; +} +//------------------------------------------------------------------------------ +// Find a start of frame (SOF) marker. +static uint8 locateSOFMarker(void) +{ + uint8 c; + + uint8 status = locateSOIMarker(); + if (status) + return status; + + status = processMarkers(&c); + if (status) + return status; + + switch (c) + { + case M_SOF2: + { + // Progressive JPEG - not supported by picojpeg (would require too + // much memory, or too many IDCT's for embedded systems). + return PJPG_UNSUPPORTED_MODE; + } + case M_SOF0: /* baseline DCT */ + { + status = readSOFMarker(); + if (status) + return status; + + break; + } + case M_SOF9: + { + return PJPG_NO_ARITHMITIC_SUPPORT; + } + case M_SOF1: /* extended sequential DCT */ + default: + { + return PJPG_UNSUPPORTED_MARKER; + } + } + + return 0; +} +//------------------------------------------------------------------------------ +// Find a start of scan (SOS) marker. +static uint8 locateSOSMarker(uint8* pFoundEOI) +{ + uint8 c; + uint8 status; + + *pFoundEOI = 0; + + status = processMarkers(&c); + if (status) + return status; + + if (c == M_EOI) + { + *pFoundEOI = 1; + return 0; + } + else if (c != M_SOS) + return PJPG_UNEXPECTED_MARKER; + + return readSOSMarker(); +} +//------------------------------------------------------------------------------ +static uint8 init(void) +{ + gImageXSize = 0; + gImageYSize = 0; + gCompsInFrame = 0; + gRestartInterval = 0; + gCompsInScan = 0; + gValidHuffTables = 0; + gValidQuantTables = 0; + gTemFlag = 0; + gInBufOfs = 0; + gInBufLeft = 0; + gBitBuf = 0; + gBitsLeft = 8; + + getBits1(8); + getBits1(8); + + return 0; +} +//------------------------------------------------------------------------------ +// This method throws back into the stream any bytes that where read +// into the bit buffer during initial marker scanning. +static void fixInBuffer(void) +{ + /* In case any 0xFF's where pulled into the buffer during marker scanning */ + + if (gBitsLeft > 0) + stuffChar((uint8)gBitBuf); + + stuffChar((uint8)(gBitBuf >> 8)); + + gBitsLeft = 8; + getBits2(8); + getBits2(8); +} +//------------------------------------------------------------------------------ +// Restart interval processing. +static uint8 processRestart(void) +{ + // Let's scan a little bit to find the marker, but not _too_ far. + // 1536 is a "fudge factor" that determines how much to scan. + uint16 i; + uint8 c = 0; + + for (i = 1536; i > 0; i--) + if (getChar() == 0xFF) + break; + + if (i == 0) + return PJPG_BAD_RESTART_MARKER; + + for ( ; i > 0; i--) + if ((c = getChar()) != 0xFF) + break; + + if (i == 0) + return PJPG_BAD_RESTART_MARKER; + + // Is it the expected marker? If not, something bad happened. + if (c != (gNextRestartNum + M_RST0)) + return PJPG_BAD_RESTART_MARKER; + + // Reset each component's DC prediction values. + gLastDC[0] = 0; + gLastDC[1] = 0; + gLastDC[2] = 0; + + gRestartsLeft = gRestartInterval; + + gNextRestartNum = (gNextRestartNum + 1) & 7; + + // Get the bit buffer going again + + gBitsLeft = 8; + getBits2(8); + getBits2(8); + + return 0; +} +//------------------------------------------------------------------------------ +// FIXME: findEOI() is not actually called at the end of the image +// (it's optional, and probably not needed on embedded devices) +static uint8 findEOI(void) +{ + uint8 c; + uint8 status; + + // Prime the bit buffer + gBitsLeft = 8; + getBits1(8); + getBits1(8); + + // The next marker _should_ be EOI + status = processMarkers(&c); + if (status) + return status; + else if (gCallbackStatus) + return gCallbackStatus; + + //gTotalBytesRead -= in_buf_left; + if (c != M_EOI) + return PJPG_UNEXPECTED_MARKER; + + return 0; +} +//------------------------------------------------------------------------------ +static uint8 checkHuffTables(void) +{ + uint8 i; + + for (i = 0; i < gCompsInScan; i++) + { + uint8 compDCTab = gCompDCTab[gCompList[i]]; + uint8 compACTab = gCompACTab[gCompList[i]] + 2; + + if ( ((gValidHuffTables & (1 << compDCTab)) == 0) || + ((gValidHuffTables & (1 << compACTab)) == 0) ) + return PJPG_UNDEFINED_HUFF_TABLE; + } + + return 0; +} +//------------------------------------------------------------------------------ +static uint8 checkQuantTables(void) +{ + uint8 i; + + for (i = 0; i < gCompsInScan; i++) + { + uint8 compQuantMask = gCompQuant[gCompList[i]] ? 2 : 1; + + if ((gValidQuantTables & compQuantMask) == 0) + return PJPG_UNDEFINED_QUANT_TABLE; + } + + return 0; +} +//------------------------------------------------------------------------------ +static uint8 initScan(void) +{ + uint8 foundEOI; + uint8 status = locateSOSMarker(&foundEOI); + if (status) + return status; + if (foundEOI) + return PJPG_UNEXPECTED_MARKER; + + status = checkHuffTables(); + if (status) + return status; + + status = checkQuantTables(); + if (status) + return status; + + gLastDC[0] = 0; + gLastDC[1] = 0; + gLastDC[2] = 0; + + if (gRestartInterval) + { + gRestartsLeft = gRestartInterval; + gNextRestartNum = 0; + } + + fixInBuffer(); + + return 0; +} +//------------------------------------------------------------------------------ +static uint8 initFrame(void) +{ + if (gCompsInFrame == 1) + { + if ((gCompHSamp[0] != 1) || (gCompVSamp[0] != 1)) + return PJPG_UNSUPPORTED_SAMP_FACTORS; + + gScanType = PJPG_GRAYSCALE; + + gMaxBlocksPerMCU = 1; + gMCUOrg[0] = 0; + + gMaxMCUXSize = 8; + gMaxMCUYSize = 8; + } + else if (gCompsInFrame == 3) + { + if ( ((gCompHSamp[1] != 1) || (gCompVSamp[1] != 1)) || + ((gCompHSamp[2] != 1) || (gCompVSamp[2] != 1)) ) + return PJPG_UNSUPPORTED_SAMP_FACTORS; + + if ((gCompHSamp[0] == 1) && (gCompVSamp[0] == 1)) + { + gScanType = PJPG_YH1V1; + + gMaxBlocksPerMCU = 3; + gMCUOrg[0] = 0; + gMCUOrg[1] = 1; + gMCUOrg[2] = 2; + + gMaxMCUXSize = 8; + gMaxMCUYSize = 8; + } + else if ((gCompHSamp[0] == 1) && (gCompVSamp[0] == 2)) + { + gScanType = PJPG_YH1V2; + + gMaxBlocksPerMCU = 4; + gMCUOrg[0] = 0; + gMCUOrg[1] = 0; + gMCUOrg[2] = 1; + gMCUOrg[3] = 2; + + gMaxMCUXSize = 8; + gMaxMCUYSize = 16; + } + else if ((gCompHSamp[0] == 2) && (gCompVSamp[0] == 1)) + { + gScanType = PJPG_YH2V1; + + gMaxBlocksPerMCU = 4; + gMCUOrg[0] = 0; + gMCUOrg[1] = 0; + gMCUOrg[2] = 1; + gMCUOrg[3] = 2; + + gMaxMCUXSize = 16; + gMaxMCUYSize = 8; + } + else if ((gCompHSamp[0] == 2) && (gCompVSamp[0] == 2)) + { + gScanType = PJPG_YH2V2; + + gMaxBlocksPerMCU = 6; + gMCUOrg[0] = 0; + gMCUOrg[1] = 0; + gMCUOrg[2] = 0; + gMCUOrg[3] = 0; + gMCUOrg[4] = 1; + gMCUOrg[5] = 2; + + gMaxMCUXSize = 16; + gMaxMCUYSize = 16; + } + else + return PJPG_UNSUPPORTED_SAMP_FACTORS; + } + else + return PJPG_UNSUPPORTED_COLORSPACE; + + gMaxMCUSPerRow = (gImageXSize + (gMaxMCUXSize - 1)) >> ((gMaxMCUXSize == 8) ? 3 : 4); + gMaxMCUSPerCol = (gImageYSize + (gMaxMCUYSize - 1)) >> ((gMaxMCUYSize == 8) ? 3 : 4); + + gNumMCUSRemaining = gMaxMCUSPerRow * gMaxMCUSPerCol; + + return 0; +} +//---------------------------------------------------------------------------- +// Winograd IDCT: 5 multiplies per row/col, up to 80 muls for the 2D IDCT + +#define PJPG_DCT_SCALE_BITS 7 + +#define PJPG_DCT_SCALE (1U << PJPG_DCT_SCALE_BITS) + +#define PJPG_DESCALE(x) PJPG_ARITH_SHIFT_RIGHT_N_16(((x) + (1U << (PJPG_DCT_SCALE_BITS - 1))), PJPG_DCT_SCALE_BITS) + +#define PJPG_WFIX(x) ((x) * PJPG_DCT_SCALE + 0.5f) + +#define PJPG_WINOGRAD_QUANT_SCALE_BITS 10 + +const uint8 gWinogradQuant[] = +{ + 128, 178, 178, 167, 246, 167, 151, 232, + 232, 151, 128, 209, 219, 209, 128, 101, + 178, 197, 197, 178, 101, 69, 139, 167, + 177, 167, 139, 69, 35, 96, 131, 151, + 151, 131, 96, 35, 49, 91, 118, 128, + 118, 91, 49, 46, 81, 101, 101, 81, + 46, 42, 69, 79, 69, 42, 35, 54, + 54, 35, 28, 37, 28, 19, 19, 10, +}; + +// Multiply quantization matrix by the Winograd IDCT scale factors +static void createWinogradQuant(int16* pQuant) +{ + uint8 i; + + for (i = 0; i < 64; i++) + { + long x = pQuant[i]; + x *= gWinogradQuant[i]; + pQuant[i] = (int16)((x + (1 << (PJPG_WINOGRAD_QUANT_SCALE_BITS - PJPG_DCT_SCALE_BITS - 1))) >> (PJPG_WINOGRAD_QUANT_SCALE_BITS - PJPG_DCT_SCALE_BITS)); + } +} + +// These multiply helper functions are the 4 types of signed multiplies needed by the Winograd IDCT. +// A smart C compiler will optimize them to use 16x8 = 24 bit muls, if not you may need to tweak +// these functions or drop to CPU specific inline assembly. + +// 1/cos(4*pi/16) +// 362, 256+106 +static PJPG_INLINE int16 imul_b1_b3(int16 w) +{ + long x = (w * 362L); + x += 128L; + return (int16)(PJPG_ARITH_SHIFT_RIGHT_8_L(x)); +} + +// 1/cos(6*pi/16) +// 669, 256+256+157 +static PJPG_INLINE int16 imul_b2(int16 w) +{ + long x = (w * 669L); + x += 128L; + return (int16)(PJPG_ARITH_SHIFT_RIGHT_8_L(x)); +} + +// 1/cos(2*pi/16) +// 277, 256+21 +static PJPG_INLINE int16 imul_b4(int16 w) +{ + long x = (w * 277L); + x += 128L; + return (int16)(PJPG_ARITH_SHIFT_RIGHT_8_L(x)); +} + +// 1/(cos(2*pi/16) + cos(6*pi/16)) +// 196, 196 +static PJPG_INLINE int16 imul_b5(int16 w) +{ + long x = (w * 196L); + x += 128L; + return (int16)(PJPG_ARITH_SHIFT_RIGHT_8_L(x)); +} + +static PJPG_INLINE uint8 clamp(int16 s) +{ + if ((uint16)s > 255U) + { + if (s < 0) + return 0; + else if (s > 255) + return 255; + } + + return (uint8)s; +} + +static void idctRows(void) +{ + uint8 i; + int16* pSrc = gCoeffBuf; + + for (i = 0; i < 8; i++) + { + if ((pSrc[1] | pSrc[2] | pSrc[3] | pSrc[4] | pSrc[5] | pSrc[6] | pSrc[7]) == 0) + { + // Short circuit the 1D IDCT if only the DC component is non-zero + int16 src0 = *pSrc; + + *(pSrc+1) = src0; + *(pSrc+2) = src0; + *(pSrc+3) = src0; + *(pSrc+4) = src0; + *(pSrc+5) = src0; + *(pSrc+6) = src0; + *(pSrc+7) = src0; + } + else + { + int16 src4 = *(pSrc+5); + int16 src7 = *(pSrc+3); + int16 x4 = src4 - src7; + int16 x7 = src4 + src7; + + int16 src5 = *(pSrc+1); + int16 src6 = *(pSrc+7); + int16 x5 = src5 + src6; + int16 x6 = src5 - src6; + + int16 tmp1 = imul_b5(x4 - x6); + int16 stg26 = imul_b4(x6) - tmp1; + + int16 x24 = tmp1 - imul_b2(x4); + + int16 x15 = x5 - x7; + int16 x17 = x5 + x7; + + int16 tmp2 = stg26 - x17; + int16 tmp3 = imul_b1_b3(x15) - tmp2; + int16 x44 = tmp3 + x24; + + int16 src0 = *(pSrc+0); + int16 src1 = *(pSrc+4); + int16 x30 = src0 + src1; + int16 x31 = src0 - src1; + + int16 src2 = *(pSrc+2); + int16 src3 = *(pSrc+6); + int16 x12 = src2 - src3; + int16 x13 = src2 + src3; + + int16 x32 = imul_b1_b3(x12) - x13; + + int16 x40 = x30 + x13; + int16 x43 = x30 - x13; + int16 x41 = x31 + x32; + int16 x42 = x31 - x32; + + *(pSrc+0) = x40 + x17; + *(pSrc+1) = x41 + tmp2; + *(pSrc+2) = x42 + tmp3; + *(pSrc+3) = x43 - x44; + *(pSrc+4) = x43 + x44; + *(pSrc+5) = x42 - tmp3; + *(pSrc+6) = x41 - tmp2; + *(pSrc+7) = x40 - x17; + } + + pSrc += 8; + } +} + +static void idctCols(void) +{ + uint8 i; + + int16* pSrc = gCoeffBuf; + + for (i = 0; i < 8; i++) + { + if ((pSrc[1*8] | pSrc[2*8] | pSrc[3*8] | pSrc[4*8] | pSrc[5*8] | pSrc[6*8] | pSrc[7*8]) == 0) + { + // Short circuit the 1D IDCT if only the DC component is non-zero + uint8 c = clamp(PJPG_DESCALE(*pSrc) + 128); + *(pSrc+0*8) = c; + *(pSrc+1*8) = c; + *(pSrc+2*8) = c; + *(pSrc+3*8) = c; + *(pSrc+4*8) = c; + *(pSrc+5*8) = c; + *(pSrc+6*8) = c; + *(pSrc+7*8) = c; + } + else + { + int16 src4 = *(pSrc+5*8); + int16 src7 = *(pSrc+3*8); + int16 x4 = src4 - src7; + int16 x7 = src4 + src7; + + int16 src5 = *(pSrc+1*8); + int16 src6 = *(pSrc+7*8); + int16 x5 = src5 + src6; + int16 x6 = src5 - src6; + + int16 tmp1 = imul_b5(x4 - x6); + int16 stg26 = imul_b4(x6) - tmp1; + + int16 x24 = tmp1 - imul_b2(x4); + + int16 x15 = x5 - x7; + int16 x17 = x5 + x7; + + int16 tmp2 = stg26 - x17; + int16 tmp3 = imul_b1_b3(x15) - tmp2; + int16 x44 = tmp3 + x24; + + int16 src0 = *(pSrc+0*8); + int16 src1 = *(pSrc+4*8); + int16 x30 = src0 + src1; + int16 x31 = src0 - src1; + + int16 src2 = *(pSrc+2*8); + int16 src3 = *(pSrc+6*8); + int16 x12 = src2 - src3; + int16 x13 = src2 + src3; + + int16 x32 = imul_b1_b3(x12) - x13; + + int16 x40 = x30 + x13; + int16 x43 = x30 - x13; + int16 x41 = x31 + x32; + int16 x42 = x31 - x32; + + // descale, convert to unsigned and clamp to 8-bit + *(pSrc+0*8) = clamp(PJPG_DESCALE(x40 + x17) + 128); + *(pSrc+1*8) = clamp(PJPG_DESCALE(x41 + tmp2) + 128); + *(pSrc+2*8) = clamp(PJPG_DESCALE(x42 + tmp3) + 128); + *(pSrc+3*8) = clamp(PJPG_DESCALE(x43 - x44) + 128); + *(pSrc+4*8) = clamp(PJPG_DESCALE(x43 + x44) + 128); + *(pSrc+5*8) = clamp(PJPG_DESCALE(x42 - tmp3) + 128); + *(pSrc+6*8) = clamp(PJPG_DESCALE(x41 - tmp2) + 128); + *(pSrc+7*8) = clamp(PJPG_DESCALE(x40 - x17) + 128); + } + + pSrc++; + } +} + +/*----------------------------------------------------------------------------*/ +static PJPG_INLINE uint8 addAndClamp(uint8 a, int16 b) +{ + b = a + b; + + if ((uint16)b > 255U) + { + if (b < 0) + return 0; + else if (b > 255) + return 255; + } + + return (uint8)b; +} +/*----------------------------------------------------------------------------*/ +static PJPG_INLINE uint8 subAndClamp(uint8 a, int16 b) +{ + b = a - b; + + if ((uint16)b > 255U) + { + if (b < 0) + return 0; + else if (b > 255) + return 255; + } + + return (uint8)b; +} +/*----------------------------------------------------------------------------*/ +// 103/256 +//R = Y + 1.402 (Cr-128) + +// 88/256, 183/256 +//G = Y - 0.34414 (Cb-128) - 0.71414 (Cr-128) + +// 198/256 +//B = Y + 1.772 (Cb-128) +/*----------------------------------------------------------------------------*/ +// Cb upsample and accumulate, 4x4 to 8x8 +static void upsampleCb(uint8 srcOfs, uint8 dstOfs) +{ + // Cb - affects G and B + uint8 x, y; + int16* pSrc = gCoeffBuf + srcOfs; + uint8* pDstG = gMCUBufG + dstOfs; + uint8* pDstB = gMCUBufB + dstOfs; + for (y = 0; y < 4; y++) + { + for (x = 0; x < 4; x++) + { + uint8 cb = (uint8)*pSrc++; + int16 cbG, cbB; + + cbG = ((cb * 88U) >> 8U) - 44U; + pDstG[0] = subAndClamp(pDstG[0], cbG); + pDstG[1] = subAndClamp(pDstG[1], cbG); + pDstG[8] = subAndClamp(pDstG[8], cbG); + pDstG[9] = subAndClamp(pDstG[9], cbG); + + cbB = (cb + ((cb * 198U) >> 8U)) - 227U; + pDstB[0] = addAndClamp(pDstB[0], cbB); + pDstB[1] = addAndClamp(pDstB[1], cbB); + pDstB[8] = addAndClamp(pDstB[8], cbB); + pDstB[9] = addAndClamp(pDstB[9], cbB); + + pDstG += 2; + pDstB += 2; + } + + pSrc = pSrc - 4 + 8; + pDstG = pDstG - 8 + 16; + pDstB = pDstB - 8 + 16; + } +} +/*----------------------------------------------------------------------------*/ +// Cb upsample and accumulate, 4x8 to 8x8 +static void upsampleCbH(uint8 srcOfs, uint8 dstOfs) +{ + // Cb - affects G and B + uint8 x, y; + int16* pSrc = gCoeffBuf + srcOfs; + uint8* pDstG = gMCUBufG + dstOfs; + uint8* pDstB = gMCUBufB + dstOfs; + for (y = 0; y < 8; y++) + { + for (x = 0; x < 4; x++) + { + uint8 cb = (uint8)*pSrc++; + int16 cbG, cbB; + + cbG = ((cb * 88U) >> 8U) - 44U; + pDstG[0] = subAndClamp(pDstG[0], cbG); + pDstG[1] = subAndClamp(pDstG[1], cbG); + + cbB = (cb + ((cb * 198U) >> 8U)) - 227U; + pDstB[0] = addAndClamp(pDstB[0], cbB); + pDstB[1] = addAndClamp(pDstB[1], cbB); + + pDstG += 2; + pDstB += 2; + } + + pSrc = pSrc - 4 + 8; + } +} +/*----------------------------------------------------------------------------*/ +// Cb upsample and accumulate, 8x4 to 8x8 +static void upsampleCbV(uint8 srcOfs, uint8 dstOfs) +{ + // Cb - affects G and B + uint8 x, y; + int16* pSrc = gCoeffBuf + srcOfs; + uint8* pDstG = gMCUBufG + dstOfs; + uint8* pDstB = gMCUBufB + dstOfs; + for (y = 0; y < 4; y++) + { + for (x = 0; x < 8; x++) + { + uint8 cb = (uint8)*pSrc++; + int16 cbG, cbB; + + cbG = ((cb * 88U) >> 8U) - 44U; + pDstG[0] = subAndClamp(pDstG[0], cbG); + pDstG[8] = subAndClamp(pDstG[8], cbG); + + cbB = (cb + ((cb * 198U) >> 8U)) - 227U; + pDstB[0] = addAndClamp(pDstB[0], cbB); + pDstB[8] = addAndClamp(pDstB[8], cbB); + + ++pDstG; + ++pDstB; + } + + pDstG = pDstG - 8 + 16; + pDstB = pDstB - 8 + 16; + } +} +/*----------------------------------------------------------------------------*/ +// 103/256 +//R = Y + 1.402 (Cr-128) + +// 88/256, 183/256 +//G = Y - 0.34414 (Cb-128) - 0.71414 (Cr-128) + +// 198/256 +//B = Y + 1.772 (Cb-128) +/*----------------------------------------------------------------------------*/ +// Cr upsample and accumulate, 4x4 to 8x8 +static void upsampleCr(uint8 srcOfs, uint8 dstOfs) +{ + // Cr - affects R and G + uint8 x, y; + int16* pSrc = gCoeffBuf + srcOfs; + uint8* pDstR = gMCUBufR + dstOfs; + uint8* pDstG = gMCUBufG + dstOfs; + for (y = 0; y < 4; y++) + { + for (x = 0; x < 4; x++) + { + uint8 cr = (uint8)*pSrc++; + int16 crR, crG; + + crR = (cr + ((cr * 103U) >> 8U)) - 179; + pDstR[0] = addAndClamp(pDstR[0], crR); + pDstR[1] = addAndClamp(pDstR[1], crR); + pDstR[8] = addAndClamp(pDstR[8], crR); + pDstR[9] = addAndClamp(pDstR[9], crR); + + crG = ((cr * 183U) >> 8U) - 91; + pDstG[0] = subAndClamp(pDstG[0], crG); + pDstG[1] = subAndClamp(pDstG[1], crG); + pDstG[8] = subAndClamp(pDstG[8], crG); + pDstG[9] = subAndClamp(pDstG[9], crG); + + pDstR += 2; + pDstG += 2; + } + + pSrc = pSrc - 4 + 8; + pDstR = pDstR - 8 + 16; + pDstG = pDstG - 8 + 16; + } +} +/*----------------------------------------------------------------------------*/ +// Cr upsample and accumulate, 4x8 to 8x8 +static void upsampleCrH(uint8 srcOfs, uint8 dstOfs) +{ + // Cr - affects R and G + uint8 x, y; + int16* pSrc = gCoeffBuf + srcOfs; + uint8* pDstR = gMCUBufR + dstOfs; + uint8* pDstG = gMCUBufG + dstOfs; + for (y = 0; y < 8; y++) + { + for (x = 0; x < 4; x++) + { + uint8 cr = (uint8)*pSrc++; + int16 crR, crG; + + crR = (cr + ((cr * 103U) >> 8U)) - 179; + pDstR[0] = addAndClamp(pDstR[0], crR); + pDstR[1] = addAndClamp(pDstR[1], crR); + + crG = ((cr * 183U) >> 8U) - 91; + pDstG[0] = subAndClamp(pDstG[0], crG); + pDstG[1] = subAndClamp(pDstG[1], crG); + + pDstR += 2; + pDstG += 2; + } + + pSrc = pSrc - 4 + 8; + } +} +/*----------------------------------------------------------------------------*/ +// Cr upsample and accumulate, 8x4 to 8x8 +static void upsampleCrV(uint8 srcOfs, uint8 dstOfs) +{ + // Cr - affects R and G + uint8 x, y; + int16* pSrc = gCoeffBuf + srcOfs; + uint8* pDstR = gMCUBufR + dstOfs; + uint8* pDstG = gMCUBufG + dstOfs; + for (y = 0; y < 4; y++) + { + for (x = 0; x < 8; x++) + { + uint8 cr = (uint8)*pSrc++; + int16 crR, crG; + + crR = (cr + ((cr * 103U) >> 8U)) - 179; + pDstR[0] = addAndClamp(pDstR[0], crR); + pDstR[8] = addAndClamp(pDstR[8], crR); + + crG = ((cr * 183U) >> 8U) - 91; + pDstG[0] = subAndClamp(pDstG[0], crG); + pDstG[8] = subAndClamp(pDstG[8], crG); + + ++pDstR; + ++pDstG; + } + + pDstR = pDstR - 8 + 16; + pDstG = pDstG - 8 + 16; + } +} +/*----------------------------------------------------------------------------*/ +// Convert Y to RGB +static void copyY(uint8 dstOfs) +{ + uint8 i; + uint8* pRDst = gMCUBufR + dstOfs; + uint8* pGDst = gMCUBufG + dstOfs; + uint8* pBDst = gMCUBufB + dstOfs; + int16* pSrc = gCoeffBuf; + + for (i = 64; i > 0; i--) + { + uint8 c = (uint8)*pSrc++; + + *pRDst++ = c; + *pGDst++ = c; + *pBDst++ = c; + } +} +/*----------------------------------------------------------------------------*/ +// Cb convert to RGB and accumulate +static void convertCb(uint8 dstOfs) +{ + uint8 i; + uint8* pDstG = gMCUBufG + dstOfs; + uint8* pDstB = gMCUBufB + dstOfs; + int16* pSrc = gCoeffBuf; + + for (i = 64; i > 0; i--) + { + uint8 cb = (uint8)*pSrc++; + int16 cbG, cbB; + + cbG = ((cb * 88U) >> 8U) - 44U; + pDstG[0] = subAndClamp(pDstG[0], cbG); + + cbB = (cb + ((cb * 198U) >> 8U)) - 227U; + pDstB[0] = addAndClamp(pDstB[0], cbB); + + ++pDstG; + ++pDstB; + } +} +/*----------------------------------------------------------------------------*/ +// Cr convert to RGB and accumulate +static void convertCr(uint8 dstOfs) +{ + uint8 i; + uint8* pDstR = gMCUBufR + dstOfs; + uint8* pDstG = gMCUBufG + dstOfs; + int16* pSrc = gCoeffBuf; + + for (i = 64; i > 0; i--) + { + uint8 cr = (uint8)*pSrc++; + int16 crR, crG; + + crR = (cr + ((cr * 103U) >> 8U)) - 179; + pDstR[0] = addAndClamp(pDstR[0], crR); + + crG = ((cr * 183U) >> 8U) - 91; + pDstG[0] = subAndClamp(pDstG[0], crG); + + ++pDstR; + ++pDstG; + } +} +/*----------------------------------------------------------------------------*/ +static void transformBlock(uint8 mcuBlock) +{ + idctRows(); + idctCols(); + + switch (gScanType) + { + case PJPG_GRAYSCALE: + { + // MCU size: 1, 1 block per MCU + copyY(0); + break; + } + case PJPG_YH1V1: + { + // MCU size: 8x8, 3 blocks per MCU + switch (mcuBlock) + { + case 0: + { + copyY(0); + break; + } + case 1: + { + convertCb(0); + break; + } + case 2: + { + convertCr(0); + break; + } + } + + break; + } + case PJPG_YH1V2: + { + // MCU size: 8x16, 4 blocks per MCU + switch (mcuBlock) + { + case 0: + { + copyY(0); + break; + } + case 1: + { + copyY(128); + break; + } + case 2: + { + upsampleCbV(0, 0); + upsampleCbV(4*8, 128); + break; + } + case 3: + { + upsampleCrV(0, 0); + upsampleCrV(4*8, 128); + break; + } + } + + break; + } + case PJPG_YH2V1: + { + // MCU size: 16x8, 4 blocks per MCU + switch (mcuBlock) + { + case 0: + { + copyY(0); + break; + } + case 1: + { + copyY(64); + break; + } + case 2: + { + upsampleCbH(0, 0); + upsampleCbH(4, 64); + break; + } + case 3: + { + upsampleCrH(0, 0); + upsampleCrH(4, 64); + break; + } + } + + break; + } + case PJPG_YH2V2: + { + // MCU size: 16x16, 6 blocks per MCU + switch (mcuBlock) + { + case 0: + { + copyY(0); + break; + } + case 1: + { + copyY(64); + break; + } + case 2: + { + copyY(128); + break; + } + case 3: + { + copyY(192); + break; + } + case 4: + { + upsampleCb(0, 0); + upsampleCb(4, 64); + upsampleCb(4*8, 128); + upsampleCb(4+4*8, 192); + break; + } + case 5: + { + upsampleCr(0, 0); + upsampleCr(4, 64); + upsampleCr(4*8, 128); + upsampleCr(4+4*8, 192); + break; + } + } + + break; + } + } +} +//------------------------------------------------------------------------------ +static void transformBlockReduce(uint8 mcuBlock) +{ + uint8 c = clamp(PJPG_DESCALE(gCoeffBuf[0]) + 128); + int16 cbG, cbB, crR, crG; + + switch (gScanType) + { + case PJPG_GRAYSCALE: + { + // MCU size: 1, 1 block per MCU + gMCUBufR[0] = c; + break; + } + case PJPG_YH1V1: + { + // MCU size: 8x8, 3 blocks per MCU + switch (mcuBlock) + { + case 0: + { + gMCUBufR[0] = c; + gMCUBufG[0] = c; + gMCUBufB[0] = c; + break; + } + case 1: + { + cbG = ((c * 88U) >> 8U) - 44U; + gMCUBufG[0] = subAndClamp(gMCUBufG[0], cbG); + + cbB = (c + ((c * 198U) >> 8U)) - 227U; + gMCUBufB[0] = addAndClamp(gMCUBufB[0], cbB); + break; + } + case 2: + { + crR = (c + ((c * 103U) >> 8U)) - 179; + gMCUBufR[0] = addAndClamp(gMCUBufR[0], crR); + + crG = ((c * 183U) >> 8U) - 91; + gMCUBufG[0] = subAndClamp(gMCUBufG[0], crG); + break; + } + } + + break; + } + case PJPG_YH1V2: + { + // MCU size: 8x16, 4 blocks per MCU + switch (mcuBlock) + { + case 0: + { + gMCUBufR[0] = c; + gMCUBufG[0] = c; + gMCUBufB[0] = c; + break; + } + case 1: + { + gMCUBufR[128] = c; + gMCUBufG[128] = c; + gMCUBufB[128] = c; + break; + } + case 2: + { + cbG = ((c * 88U) >> 8U) - 44U; + gMCUBufG[0] = subAndClamp(gMCUBufG[0], cbG); + gMCUBufG[128] = subAndClamp(gMCUBufG[128], cbG); + + cbB = (c + ((c * 198U) >> 8U)) - 227U; + gMCUBufB[0] = addAndClamp(gMCUBufB[0], cbB); + gMCUBufB[128] = addAndClamp(gMCUBufB[128], cbB); + + break; + } + case 3: + { + crR = (c + ((c * 103U) >> 8U)) - 179; + gMCUBufR[0] = addAndClamp(gMCUBufR[0], crR); + gMCUBufR[128] = addAndClamp(gMCUBufR[128], crR); + + crG = ((c * 183U) >> 8U) - 91; + gMCUBufG[0] = subAndClamp(gMCUBufG[0], crG); + gMCUBufG[128] = subAndClamp(gMCUBufG[128], crG); + + break; + } + } + break; + } + case PJPG_YH2V1: + { + // MCU size: 16x8, 4 blocks per MCU + switch (mcuBlock) + { + case 0: + { + gMCUBufR[0] = c; + gMCUBufG[0] = c; + gMCUBufB[0] = c; + break; + } + case 1: + { + gMCUBufR[64] = c; + gMCUBufG[64] = c; + gMCUBufB[64] = c; + break; + } + case 2: + { + cbG = ((c * 88U) >> 8U) - 44U; + gMCUBufG[0] = subAndClamp(gMCUBufG[0], cbG); + gMCUBufG[64] = subAndClamp(gMCUBufG[64], cbG); + + cbB = (c + ((c * 198U) >> 8U)) - 227U; + gMCUBufB[0] = addAndClamp(gMCUBufB[0], cbB); + gMCUBufB[64] = addAndClamp(gMCUBufB[64], cbB); + + break; + } + case 3: + { + crR = (c + ((c * 103U) >> 8U)) - 179; + gMCUBufR[0] = addAndClamp(gMCUBufR[0], crR); + gMCUBufR[64] = addAndClamp(gMCUBufR[64], crR); + + crG = ((c * 183U) >> 8U) - 91; + gMCUBufG[0] = subAndClamp(gMCUBufG[0], crG); + gMCUBufG[64] = subAndClamp(gMCUBufG[64], crG); + + break; + } + } + break; + } + case PJPG_YH2V2: + { + // MCU size: 16x16, 6 blocks per MCU + switch (mcuBlock) + { + case 0: + { + gMCUBufR[0] = c; + gMCUBufG[0] = c; + gMCUBufB[0] = c; + break; + } + case 1: + { + gMCUBufR[64] = c; + gMCUBufG[64] = c; + gMCUBufB[64] = c; + break; + } + case 2: + { + gMCUBufR[128] = c; + gMCUBufG[128] = c; + gMCUBufB[128] = c; + break; + } + case 3: + { + gMCUBufR[192] = c; + gMCUBufG[192] = c; + gMCUBufB[192] = c; + break; + } + case 4: + { + cbG = ((c * 88U) >> 8U) - 44U; + gMCUBufG[0] = subAndClamp(gMCUBufG[0], cbG); + gMCUBufG[64] = subAndClamp(gMCUBufG[64], cbG); + gMCUBufG[128] = subAndClamp(gMCUBufG[128], cbG); + gMCUBufG[192] = subAndClamp(gMCUBufG[192], cbG); + + cbB = (c + ((c * 198U) >> 8U)) - 227U; + gMCUBufB[0] = addAndClamp(gMCUBufB[0], cbB); + gMCUBufB[64] = addAndClamp(gMCUBufB[64], cbB); + gMCUBufB[128] = addAndClamp(gMCUBufB[128], cbB); + gMCUBufB[192] = addAndClamp(gMCUBufB[192], cbB); + + break; + } + case 5: + { + crR = (c + ((c * 103U) >> 8U)) - 179; + gMCUBufR[0] = addAndClamp(gMCUBufR[0], crR); + gMCUBufR[64] = addAndClamp(gMCUBufR[64], crR); + gMCUBufR[128] = addAndClamp(gMCUBufR[128], crR); + gMCUBufR[192] = addAndClamp(gMCUBufR[192], crR); + + crG = ((c * 183U) >> 8U) - 91; + gMCUBufG[0] = subAndClamp(gMCUBufG[0], crG); + gMCUBufG[64] = subAndClamp(gMCUBufG[64], crG); + gMCUBufG[128] = subAndClamp(gMCUBufG[128], crG); + gMCUBufG[192] = subAndClamp(gMCUBufG[192], crG); + + break; + } + } + break; + } + } +} +//------------------------------------------------------------------------------ +static uint8 decodeNextMCU(void) +{ + uint8 status; + uint8 mcuBlock; + + if (gRestartInterval) + { + if (gRestartsLeft == 0) + { + status = processRestart(); + if (status) + return status; + } + gRestartsLeft--; + } + + for (mcuBlock = 0; mcuBlock < gMaxBlocksPerMCU; mcuBlock++) + { + uint8 componentID = gMCUOrg[mcuBlock]; + uint8 compQuant = gCompQuant[componentID]; + uint8 compDCTab = gCompDCTab[componentID]; + uint8 numExtraBits, compACTab, k; + const int16* pQ = compQuant ? gQuant1 : gQuant0; + uint16 r, dc; + + uint8 s = huffDecode(compDCTab ? &gHuffTab1 : &gHuffTab0, compDCTab ? gHuffVal1 : gHuffVal0); + + r = 0; + numExtraBits = s & 0xF; + if (numExtraBits) + r = getBits2(numExtraBits); + dc = huffExtend(r, s); + + dc = dc + gLastDC[componentID]; + gLastDC[componentID] = dc; + + gCoeffBuf[0] = dc * pQ[0]; + + compACTab = gCompACTab[componentID]; + + if (gReduce) + { + // Decode, but throw out the AC coefficients in reduce mode. + for (k = 1; k < 64; k++) + { + s = huffDecode(compACTab ? &gHuffTab3 : &gHuffTab2, compACTab ? gHuffVal3 : gHuffVal2); + + numExtraBits = s & 0xF; + if (numExtraBits) + getBits2(numExtraBits); + + r = s >> 4; + s &= 15; + + if (s) + { + if (r) + { + if ((k + r) > 63) + return PJPG_DECODE_ERROR; + + k = (uint8)(k + r); + } + } + else + { + if (r == 15) + { + if ((k + 16) > 64) + return PJPG_DECODE_ERROR; + + k += (16 - 1); // - 1 because the loop counter is k + } + else + break; + } + } + + transformBlockReduce(mcuBlock); + } + else + { + // Decode and dequantize AC coefficients + for (k = 1; k < 64; k++) + { + uint16 extraBits; + + s = huffDecode(compACTab ? &gHuffTab3 : &gHuffTab2, compACTab ? gHuffVal3 : gHuffVal2); + + extraBits = 0; + numExtraBits = s & 0xF; + if (numExtraBits) + extraBits = getBits2(numExtraBits); + + r = s >> 4; + s &= 15; + + if (s) + { + int16 ac; + + if (r) + { + if ((k + r) > 63) + return PJPG_DECODE_ERROR; + + while (r) + { + gCoeffBuf[ZAG[k++]] = 0; + r--; + } + } + + ac = huffExtend(extraBits, s); + + gCoeffBuf[ZAG[k]] = ac * pQ[k]; + } + else + { + if (r == 15) + { + if ((k + 16) > 64) + return PJPG_DECODE_ERROR; + + for (r = 16; r > 0; r--) + gCoeffBuf[ZAG[k++]] = 0; + + k--; // - 1 because the loop counter is k + } + else + break; + } + } + + while (k < 64) + gCoeffBuf[ZAG[k++]] = 0; + + transformBlock(mcuBlock); + } + } + + return 0; +} +//------------------------------------------------------------------------------ +unsigned char pjpeg_decode_mcu(void) +{ + uint8 status; + + if (gCallbackStatus) + return gCallbackStatus; + + if (!gNumMCUSRemaining) + return PJPG_NO_MORE_BLOCKS; + + status = decodeNextMCU(); + if ((status) || (gCallbackStatus)) + return gCallbackStatus ? gCallbackStatus : status; + + gNumMCUSRemaining--; + + return 0; +} +//------------------------------------------------------------------------------ +unsigned char pjpeg_decode_init(pjpeg_image_info_t *pInfo, pjpeg_need_bytes_callback_t pNeed_bytes_callback, void *pCallback_data, unsigned char reduce) +{ + uint8 status; + + pInfo->m_width = 0; pInfo->m_height = 0; pInfo->m_comps = 0; + pInfo->m_MCUSPerRow = 0; pInfo->m_MCUSPerCol = 0; + pInfo->m_scanType = PJPG_GRAYSCALE; + pInfo->m_MCUWidth = 0; pInfo->m_MCUHeight = 0; + pInfo->m_pMCUBufR = (unsigned char*)0; pInfo->m_pMCUBufG = (unsigned char*)0; pInfo->m_pMCUBufB = (unsigned char*)0; + + g_pNeedBytesCallback = pNeed_bytes_callback; + g_pCallback_data = pCallback_data; + gCallbackStatus = 0; + gReduce = reduce; + + status = init(); + if ((status) || (gCallbackStatus)) + return gCallbackStatus ? gCallbackStatus : status; + + status = locateSOFMarker(); + if ((status) || (gCallbackStatus)) + return gCallbackStatus ? gCallbackStatus : status; + + status = initFrame(); + if ((status) || (gCallbackStatus)) + return gCallbackStatus ? gCallbackStatus : status; + + status = initScan(); + if ((status) || (gCallbackStatus)) + return gCallbackStatus ? gCallbackStatus : status; + + pInfo->m_width = gImageXSize; pInfo->m_height = gImageYSize; pInfo->m_comps = gCompsInFrame; + pInfo->m_scanType = gScanType; + pInfo->m_MCUSPerRow = gMaxMCUSPerRow; pInfo->m_MCUSPerCol = gMaxMCUSPerCol; + pInfo->m_MCUWidth = gMaxMCUXSize; pInfo->m_MCUHeight = gMaxMCUYSize; + pInfo->m_pMCUBufR = gMCUBufR; pInfo->m_pMCUBufG = gMCUBufG; pInfo->m_pMCUBufB = gMCUBufB; + + return 0; +} diff --git a/src/utility/JPEGDecoder/src/picojpeg.h b/src/utility/JPEGDecoder/src/picojpeg.h new file mode 100644 index 0000000..ef788d0 --- /dev/null +++ b/src/utility/JPEGDecoder/src/picojpeg.h @@ -0,0 +1,129 @@ +//------------------------------------------------------------------------------ +// picojpeg - Public domain, Rich Geldreich +//------------------------------------------------------------------------------ +#ifndef PICOJPEG_H +#define PICOJPEG_H + +#ifdef __cplusplus +extern "C" { +#endif + +// Error codes +enum +{ + PJPG_NO_MORE_BLOCKS = 1, + PJPG_BAD_DHT_COUNTS, + PJPG_BAD_DHT_INDEX, + PJPG_BAD_DHT_MARKER, + PJPG_BAD_DQT_MARKER, + PJPG_BAD_DQT_TABLE, + PJPG_BAD_PRECISION, + PJPG_BAD_HEIGHT, + PJPG_BAD_WIDTH, + PJPG_TOO_MANY_COMPONENTS, + PJPG_BAD_SOF_LENGTH, + PJPG_BAD_VARIABLE_MARKER, + PJPG_BAD_DRI_LENGTH, + PJPG_BAD_SOS_LENGTH, + PJPG_BAD_SOS_COMP_ID, + PJPG_W_EXTRA_BYTES_BEFORE_MARKER, + PJPG_NO_ARITHMITIC_SUPPORT, + PJPG_UNEXPECTED_MARKER, + PJPG_NOT_JPEG, + PJPG_UNSUPPORTED_MARKER, + PJPG_BAD_DQT_LENGTH, + PJPG_TOO_MANY_BLOCKS, + PJPG_UNDEFINED_QUANT_TABLE, + PJPG_UNDEFINED_HUFF_TABLE, + PJPG_NOT_SINGLE_SCAN, + PJPG_UNSUPPORTED_COLORSPACE, + PJPG_UNSUPPORTED_SAMP_FACTORS, + PJPG_DECODE_ERROR, + PJPG_BAD_RESTART_MARKER, + PJPG_ASSERTION_ERROR, + PJPG_BAD_SOS_SPECTRAL, + PJPG_BAD_SOS_SUCCESSIVE, + PJPG_STREAM_READ_ERROR, + PJPG_NOTENOUGHMEM, + PJPG_UNSUPPORTED_COMP_IDENT, + PJPG_UNSUPPORTED_QUANT_TABLE, + PJPG_UNSUPPORTED_MODE, // picojpeg doesn't support progressive JPEG's +}; + +// Scan types +typedef enum +{ + PJPG_GRAYSCALE, + PJPG_YH1V1, + PJPG_YH2V1, + PJPG_YH1V2, + PJPG_YH2V2 +} pjpeg_scan_type_t; + +typedef struct +{ + // Image resolution + int m_width; + int m_height; + + // Number of components (1 or 3) + int m_comps; + + // Total number of minimum coded units (MCU's) per row/col. + int m_MCUSPerRow; + int m_MCUSPerCol; + + // Scan type + pjpeg_scan_type_t m_scanType; + + // MCU width/height in pixels (each is either 8 or 16 depending on the scan type) + int m_MCUWidth; + int m_MCUHeight; + + // m_pMCUBufR, m_pMCUBufG, and m_pMCUBufB are pointers to internal MCU Y or RGB pixel component buffers. + // Each time pjpegDecodeMCU() is called successfully these buffers will be filled with 8x8 pixel blocks of Y or RGB pixels. + // Each MCU consists of (m_MCUWidth/8)*(m_MCUHeight/8) Y/RGB blocks: 1 for greyscale/no subsampling, 2 for H1V2/H2V1, or 4 blocks for H2V2 sampling factors. + // Each block is a contiguous array of 64 (8x8) bytes of a single component: either Y for grayscale images, or R, G or B components for color images. + // + // The 8x8 pixel blocks are organized in these byte arrays like this: + // + // PJPG_GRAYSCALE: Each MCU is decoded to a single block of 8x8 grayscale pixels. + // Only the values in m_pMCUBufR are valid. Each 8 bytes is a row of pixels (raster order: left to right, top to bottom) from the 8x8 block. + // + // PJPG_H1V1: Each MCU contains is decoded to a single block of 8x8 RGB pixels. + // + // PJPG_YH2V1: Each MCU is decoded to 2 blocks, or 16x8 pixels. + // The 2 RGB blocks are at byte offsets: 0, 64 + // + // PJPG_YH1V2: Each MCU is decoded to 2 blocks, or 8x16 pixels. + // The 2 RGB blocks are at byte offsets: 0, + // 128 + // + // PJPG_YH2V2: Each MCU is decoded to 4 blocks, or 16x16 pixels. + // The 2x2 block array is organized at byte offsets: 0, 64, + // 128, 192 + // + // It is up to the caller to copy or blit these pixels from these buffers into the destination bitmap. + unsigned char *m_pMCUBufR; + unsigned char *m_pMCUBufG; + unsigned char *m_pMCUBufB; +} pjpeg_image_info_t; + +typedef unsigned char (*pjpeg_need_bytes_callback_t)(unsigned char* pBuf, unsigned char buf_size, unsigned char *pBytes_actually_read, void *pCallback_data); + +// Initializes the decompressor. Returns 0 on success, or one of the above error codes on failure. +// pNeed_bytes_callback will be called to fill the decompressor's internal input buffer. +// If reduce is 1, only the first pixel of each block will be decoded. This mode is much faster because it skips the AC dequantization, IDCT and chroma upsampling of every image pixel. +// Not thread safe. +unsigned char pjpeg_decode_init(pjpeg_image_info_t *pInfo, pjpeg_need_bytes_callback_t pNeed_bytes_callback, void *pCallback_data, unsigned char reduce); + +// Decompresses the file's next MCU. Returns 0 on success, PJPG_NO_MORE_BLOCKS if no more blocks are available, or an error code. +// Must be called a total of m_MCUSPerRow*m_MCUSPerCol times to completely decompress the image. +// Not thread safe. +unsigned char pjpeg_decode_mcu(void); + +#ifdef __cplusplus +} +#endif + +#endif // PICOJPEG_H