Skip to content

Commit

Permalink
Merge pull request #131 from martinberlin/develop
Browse files Browse the repository at this point in the history
Adding library.json for Platformio ESP-IDF build & new JPEGDEC example
  • Loading branch information
vroland committed Jan 23, 2022
2 parents f35c282 + 6410dba commit 2b550cd
Show file tree
Hide file tree
Showing 14 changed files with 4,753 additions and 52 deletions.
4 changes: 4 additions & 0 deletions examples/www-image/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
cmake_minimum_required(VERSION 3.5)

set(EXTRA_COMPONENT_DIRS "../../src/")

include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(www-image_example)

# This is just to avoid that components/jpegdec includes Arduino.h
idf_build_set_property(COMPILE_OPTIONS "-D __LINUX__" APPEND)
35 changes: 26 additions & 9 deletions examples/www-image/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,37 @@ After discussing the idea of collaborating adding an WiFi download and render ex
we decided to also add a JPG decoding example suggested by @vroland.

**jpg-render.c**
Takes aprox. 1.5 to 2 seconds to download a 200Kb jpeg.
Takes aprox. 1.5 to 2 seconds to download a 200Kb jpeg. Additionally another second to decompress and render the image using EPDiy epd_draw_pixel()

**jpgdec-render.cpp**
This version uses [Bitbank2 jpeg decoder](https://github.com/bitbank2/JPEGDEC) as an external component, please run: **git submodule update --init --recursive**
in order to download it and it will be placed in components/jpegdec. This is a second C++ example that uses JPEGDEC a decoder than is faster and in this particular example we are also adding the possibility of copying each entire row of the JPG to the epaper framebuffer (Using **JPEG_CPY_FRAMEBUFFER** set to true)

Copying the entire rows to the framebuffer reduces almost completely the rendering time at the cost of loosing software rotation and gamma correction, but that might be not needed if you want to render an image as fast as possible.

Additionally another second to decompress and render the image using EPDiy epd_draw_pixel()

Detailed statistics:
Detailed statistics using jpg-render.c:

```
48772 bytes read from https://loremflickr.com/960/540
I (10676) decode: 757 ms . image decompression
I (11401) render: 297 ms - jpeg draw
I (11402) www-dw: 1728 ms - download
I (12621) total: 2782 ms - total time spent
decode: 757 ms . image decompression
render: 297 ms - jpeg draw
www-dw: 1728 ms - download
total: 2782 ms - total time spent
```

**Note:** Statistics where taken with the 4.7" Lilygo display 960x540 pixels and may be significantly higher using bigger displays.

JPEGDEC is useful to make a difference when decoding larger images. Here an example decoding a 250 Kb image:

```
250Kb bytes read from http://img.cale.es/jpg/fasani/603fcbb59bff8
decode: 593 ms - 1024x768 image decode MCUs:48
render: 1 ms - copying pix (memcpy rows)
www-dw: 1134 ms - download
total: 1726 ms
```

Building it
===========

Expand Down Expand Up @@ -52,7 +66,10 @@ The CA root cert is the last cert given in the chain of certs.
To embed it in the app binary, the PEM file is named in the component.mk COMPONENT_EMBED_TXTFILES variable. This is already done for this random picture as an example.
**Important note about secure https**
Https is proved to work on stable ESP-IDF v4.2 branch. Using latest master I've always had resets and panic restarts, only working randomly. Maybe it's an issue will be fixed.
Https is proved to work on stable ESP-IDF v4.2 branch. Please Note that for IDF versions >= 4.3 it needs to have VALIDATE_SSL_CERTIFICATE set to true.
In case you want to allow insecure requests please follow this:
In menu choose Component config->ESP LTS-> (enable these options) "Allow potentially insecure options" and then "Skip server verification by default"
Also needs the main Stack to be bigger otherwise the embedTLS validation fails:
Just 1Kb makes it work:
Expand Down
8 changes: 8 additions & 0 deletions examples/www-image/components/jpegdec/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
set(srcs
"JPEGDEC.cpp"
"jpeg.c"
)
idf_component_register(SRCS ${srcs}
REQUIRES "jpegdec"
INCLUDE_DIRS "include"
)
199 changes: 199 additions & 0 deletions examples/www-image/components/jpegdec/JPEGDEC.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
//
// JPEG Decoder
//
// written by Larry Bank
// bitbank@pobox.com
// Arduino port started 8/2/2020
// Original JPEG code written 26+ years ago :)
// The goal of this code is to decode baseline JPEG images
// using no more than 18K of RAM (if sent directly to an LCD display)
//
// Copyright 2020 BitBank Software, Inc. All Rights Reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===========================================================================
//
#include "JPEGDEC.h"

// forward references
JPEG_STATIC int JPEGInit(JPEGIMAGE *pJPEG);
JPEG_STATIC int JPEGParseInfo(JPEGIMAGE *pPage, int bExtractThumb);
JPEG_STATIC void JPEGGetMoreData(JPEGIMAGE *pPage);
JPEG_STATIC int DecodeJPEG(JPEGIMAGE *pImage);

// Include the C code which does the actual work
#include "jpeg.c"

void JPEGDEC::setPixelType(int iType)
{
if (iType >= 0 && iType < INVALID_PIXEL_TYPE)
_jpeg.ucPixelType = (uint8_t)iType;
else
_jpeg.iError = JPEG_INVALID_PARAMETER;
} /* setPixelType() */

void JPEGDEC::setMaxOutputSize(int iMaxMCUs)
{
if (iMaxMCUs < 1)
iMaxMCUs = 1; // don't allow invalid value
_jpeg.iMaxMCUs = iMaxMCUs;
} /* setMaxOutputSize() */
//
// Memory initialization
//
int JPEGDEC::openRAM(uint8_t *pData, int iDataSize, JPEG_DRAW_CALLBACK *pfnDraw)
{
memset(&_jpeg, 0, sizeof(JPEGIMAGE));
_jpeg.ucMemType = JPEG_MEM_RAM;
_jpeg.pfnRead = readRAM;
_jpeg.pfnSeek = seekMem;
_jpeg.pfnDraw = pfnDraw;
_jpeg.pfnOpen = NULL;
_jpeg.pfnClose = NULL;
_jpeg.JPEGFile.iSize = iDataSize;
_jpeg.JPEGFile.pData = pData;
_jpeg.iMaxMCUs = 1000; // set to an unnaturally high value to start
return JPEGInit(&_jpeg);
} /* openRAM() */

int JPEGDEC::openFLASH(uint8_t *pData, int iDataSize, JPEG_DRAW_CALLBACK *pfnDraw)
{
memset(&_jpeg, 0, sizeof(JPEGIMAGE));
_jpeg.ucMemType = JPEG_MEM_FLASH;
_jpeg.pfnRead = readFLASH;
_jpeg.pfnSeek = seekMem;
_jpeg.pfnDraw = pfnDraw;
_jpeg.pfnOpen = NULL;
_jpeg.pfnClose = NULL;
_jpeg.JPEGFile.iSize = iDataSize;
_jpeg.JPEGFile.pData = pData;
_jpeg.iMaxMCUs = 1000; // set to an unnaturally high value to start
return JPEGInit(&_jpeg);
} /* openRAM() */

int JPEGDEC::getOrientation()
{
return (int)_jpeg.ucOrientation;
} /* getOrientation() */

int JPEGDEC::getLastError()
{
return _jpeg.iError;
} /* getLastError() */

int JPEGDEC::getWidth()
{
return _jpeg.iWidth;
} /* getWidth() */

int JPEGDEC::getHeight()
{
return _jpeg.iHeight;
} /* getHeight() */

int JPEGDEC::hasThumb()
{
return (int)_jpeg.ucHasThumb;
} /* hasThumb() */

int JPEGDEC::getThumbWidth()
{
return _jpeg.iThumbWidth;
} /* getThumbWidth() */

int JPEGDEC::getThumbHeight()
{
return _jpeg.iThumbHeight;
} /* getThumbHeight() */

int JPEGDEC::getBpp()
{
return (int)_jpeg.ucBpp;
} /* getBpp() */

int JPEGDEC::getSubSample()
{
return (int)_jpeg.ucSubSample;
} /* getSubSample() */

//
// File (SD/MMC) based initialization
//
int JPEGDEC::open(const char *szFilename, JPEG_OPEN_CALLBACK *pfnOpen, JPEG_CLOSE_CALLBACK *pfnClose, JPEG_READ_CALLBACK *pfnRead, JPEG_SEEK_CALLBACK *pfnSeek, JPEG_DRAW_CALLBACK *pfnDraw)
{
memset(&_jpeg, 0, sizeof(JPEGIMAGE));
_jpeg.pfnRead = pfnRead;
_jpeg.pfnSeek = pfnSeek;
_jpeg.pfnDraw = pfnDraw;
_jpeg.pfnOpen = pfnOpen;
_jpeg.pfnClose = pfnClose;
_jpeg.iMaxMCUs = 1000; // set to an unnaturally high value to start
_jpeg.JPEGFile.fHandle = (*pfnOpen)(szFilename, &_jpeg.JPEGFile.iSize);
if (_jpeg.JPEGFile.fHandle == NULL)
return 0;
return JPEGInit(&_jpeg);

} /* open() */

#ifdef FS_H
static int32_t FileRead(JPEGFILE *handle, uint8_t *buffer, int32_t length)
{
return ((File *)(handle->fHandle))->read(buffer, length);
}
static int32_t FileSeek(JPEGFILE *handle, int32_t position)
{
return ((File *)(handle->fHandle))->seek(position);
}
static void FileClose(void *handle)
{
((File *)handle)->close();
}

int JPEGDEC::open(File &file, JPEG_DRAW_CALLBACK *pfnDraw)
{
if (!file) return 0;
memset(&_jpeg, 0, sizeof(JPEGIMAGE));
_jpeg.pfnRead = FileRead;
_jpeg.pfnSeek = FileSeek;
_jpeg.pfnClose = FileClose;
_jpeg.pfnDraw = pfnDraw;
_jpeg.iMaxMCUs = 1000;
_jpeg.JPEGFile.fHandle = &file;
_jpeg.JPEGFile.iSize = file.size();
return JPEGInit(&_jpeg);
}
#endif // FS_H

void JPEGDEC::close()
{
if (_jpeg.pfnClose)
(*_jpeg.pfnClose)(_jpeg.JPEGFile.fHandle);
} /* close() */

//
// Decode the image
// returns:
// 1 = good result
// 0 = error
//
int JPEGDEC::decode(int x, int y, int iOptions)
{
_jpeg.iXOffset = x;
_jpeg.iYOffset = y;
_jpeg.iOptions = iOptions;
return DecodeJPEG(&_jpeg);
} /* decode() */

int JPEGDEC::decodeDither(uint8_t *pDither, int iOptions)
{
_jpeg.iOptions = iOptions;
_jpeg.pDitherBuffer = pDither;
return DecodeJPEG(&_jpeg);
}

0 comments on commit 2b550cd

Please sign in to comment.