update to 1.3.0 version

This commit is contained in:
fsender
2023-11-06 15:39:13 +08:00
parent b5d77bc054
commit a8b2540468
31 changed files with 903 additions and 635 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 980 KiB

After

Width:  |  Height:  |  Size: 221 KiB

View File

@@ -0,0 +1,388 @@
/******************** F r i e n d s h i p E n d e r ********************
* 本程序隶属于 Readguy 开源项目, 请尊重开源开发者, 也就是我FriendshipEnder.
* 如果有条件请到 extra/artset/reward 中扫描打赏,否则请在 Bilibili 上支持我.
* 项目交流QQ群: 926824162 (萌新可以进来问问题的哟)
* 郑重声明: 未经授权还请不要商用本开源项目编译出的程序.
*
* @file ex06_Image.ino
* @author FriendshipEnder (f_ender@163.com), Bilibili: FriendshipEnder
* @version 1.0
* @date create: 2023-11-01
* last modify: 2023-11-06
* @brief ReadGuy 图片显示功能演示.
*
* - 演示如何将比较大的图片通过多种方法显示到屏幕上.
* - 运行的会很缓慢, 因为示例的图片文件比较大.
* 1. 在运行过ex01或者ex02的开发板上 编译烧录本程序.
* 2. 将该项目data文件夹内的所有文件放置于SD卡的根目录上.
*
* {0} 代码食用注意事项:
* 这一部分的代码很难读, 或者按维莫斯小姐的说法, 很 "抽象" .
* 如果你要开始一行一行读取下去, 不妨先在这里停顿一下, 看一下一些需要你提前知道的东西.
*
* {1} 画布, 或者叫sprite. 是一块专门存储图像的数据结构.
* 画布需要大量的内存来存放图像的像素, 但是你可以对画布进行任何图像操作, 就像是在墨水屏上能进行的操作一样多.
* 画布类型的数据结构至少需要包含3个要素: 宽度, 高度, 每像素需要的字节数.
* 画布需要的内存为 宽度*高度*每像素需要的字节数. (1bit图像需要8分之一字节)
* 在画布上可以进行几乎任何图像操作, 就像是在墨水屏上能进行的操作一样多.
* 而且你还可以获取像素的颜色 (这和你创建的画布的颜色格式有关).
* 画布可以随时被快速的显示到墨水屏上, 显示完了之后再使用display方法, 你刚才创建的画布就显示出来了.
*
* {2} 屏幕缓存. 你就从来没好奇过, 墨水屏上怎么呈现图像的? 我好奇过.
* 我翻看了里面那些错综复杂又是深奥的C++骚操作又是类继承又是虚函数指针...反正就是抽象而且难懂的的源代码,
* 简而言之, 墨水屏显示的原理就是墨水屏驱动里面也有个画布, 不过这个画布是1bit的(每8像素消耗1字节内存).
* 所有对墨水屏进行的操作其实都是对那个内部的画布进行的操作.
* 在你使用墨水屏的display方法时, 呃, 墨水屏的驱动程序可以读取到这个屏幕缓存的所有字节.
* 并且根据驱动手册的方法将这个画布上的字节发送到屏幕上.
* 发送完成之后再通过一些代码, 墨水屏就会刷屏, 将这个屏幕缓存里的内容显示出来了.
* 维莫斯小姐: 苏滴嘶内~
* 下面的库其实用了一些更加骚气的方法, 直接读取了这块画布, 从而能允许你在ESP8266上也可以...显示不小的图片
*
* {3} 灰度显示. ReadGuy这个库的一大特色就是支持灰度显示.
* 欸, 可是屏幕缓存画布只有1bit啊, 怎么显示灰度啊?
* 不用急, 原理也不难.
* 16级灰度颜色的刷屏分为15个步骤. 因为白色不需要刷.
* 在开始刷屏之前, 务必进行一次慢刷, 确保所有的白色像素都足够白.
* 第一次刷屏, 通常会刷最浅的灰色.
* 此时, 首先将屏幕缓存内所有需要显示为这个颜色的像素全写入为黑色(只是名义上的黑色, 实际上是设定的颜色).
* 再初始化墨水屏的刷屏功能.
* - 此处需要额外设定刷屏的颜色的参数. 此参数设定是可以由驱动程序自动完成的, 不过也可以通过你调用函数完成.
* - 用户只需要用简单的setDepth函数设置颜色即可. 用户设定的颜色深度通常用于显示字符串或者形状等GUI控件.
* - 刷图程序的灰度图片刷新功能会自动控制刷屏颜色, 不需要手动调用.
* 最后刷屏. 因为之前刷图程序已经设定过刷的颜色了, 所以此时刷屏呈现的颜色就是之前设定的颜色.
* 第2-15次刷屏原理类似.
* 每次追加黑色像素, 追加的像素就是来显示灰度的像素. 而原本的黑色像素就是之前那些灰度颜色的像素.
*
* \*(^_^)*/ /** 我相信你应该还不懂. 我就举个例子吧.
* 比如要显示一个渐变色, 从左到右为白色→黑色的渐变图.
* 首先进行一次慢刷, 确保要刷图的地方都是纯洁的白色.
* ≡=-(1)-=≡
* 筛选出最浅的灰色设为灰1, (灰X代表灰度颜色, 灰15代表黑色)
* 再把图片内所有颜色和灰1最接近的颜色写入到屏幕缓存内.
* 此时屏幕缓存内的"黑色"部分都是与灰1最接近的颜色, 其他颜色均为"白色"部分.
* 刷好了之后, 进行一次刷屏. (系统自动调用setDepth(1)来保证接下来图片的黑色部分其实是灰1).
* ≡=-(2)-=≡
* 筛选出比最浅的灰色深一点的颜色设为灰2, 把图片内和灰2最接近的颜色写入屏幕缓存, 但保持原本代表灰1的"黑色"不变.
* (也就是说, 把灰2的像素写入之后, 屏幕缓存的黑色像素只多不少. 写入过程中, 只能是白→黑, 不存在黑→白)
* 此时屏幕缓存内的"黑色"部分包含了与灰2最接近的颜色, 和刚才刷完的与灰1最接近的颜色. 其他颜色均为"白色"部分.
* 刷好了之后, 进行一次刷屏. (系统自动调用setDepth(2)来保证接下来图片的黑色部分其实是灰2)
* 注意屏幕显示, 原本的灰1并没有颜色变化, 变化的都是本次追加的"灰2"代表的"黑色".
* 追加的像素会显示为新设定的颜色.
* ≡=-(3)-=≡
* 对于灰3, 和灰2一样, 追加显示到屏幕缓存内.
* (也就是说, 屏幕缓存的黑色像素还是只多不少. 写入过程中, 只能是白→黑, 不存在黑→白)
* 此时屏幕缓存内的"黑色"部分包含了与灰3, 灰2和灰1最接近的颜色. 其他颜色均为"白色"部分.
* 设置颜色深度, 刷屏.
* 对于灰4~灰15, 都这么做
* ≡=-(5)-=≡
* 刷完灰15之后, 灰度图就刷完了.
*
* {4} 自定义刷屏函数. 到底是什么决定了刷屏时的每一像素是什么颜色? 是黑是白?
* 其实刷屏的本质就是通过调用一个外部函数.
* 该函数可以根据输入的像素坐标位置来决定输出的像素颜色.
* 为了简化程序调用过程并提高调用速度, 此处的像素坐标位置参数为一个整数(而不是两个)
* 至于该怎么调用这个函数, 并不是你需要了解的事情.
*
* @note 重要消息: 这是一个实验性功能. 可能你所使用的LGFX库版本较旧而无法通过编译.
* 如果你的项目中无法成功编译源码中的setBuffer, 请更改LovyanGFX库的函数!
* 位于文件 LovyanGFX/src/lgfx/v1/LGFX_Sprite.hpp
* 第155行 void setBuffer 函数:
* 修改为如下内容并保存
*
``` C++
void setBuffer(void* buffer, int32_t w, int32_t h, color_depth_t bpp = rgb565_2Byte)
{
deleteSprite();
if (bpp != 0) {
_write_conv.setColorDepth(bpp);
_read_conv = _write_conv;
_panel_sprite.setColorDepth(bpp);
}
_panel_sprite.setBuffer(buffer, w, h, &_write_conv);
_img = _panel_sprite.getBuffer();
_sw = w;
_clip_r = w - 1;
_xpivot = w >> 1;
_sh = h;
_clip_b = h - 1;
_ypivot = h >> 1;
}
```
* 完成后请再次尝试编译
* [已经向lovyan03/LovyanGFX发布issue, 等待解决]
*
* @attention
* Copyright (c) 2022-2023 FriendshipEnder
*
* Apache License, Version 2.0
*
* 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 <Arduino.h> //arduino功能基础库. 在platformIO平台上此语句不可或缺
#include "readguy.h" //包含readguy_driver 基础驱动库
#include "guy_image.h" //包含 readguy_image 图片功能库. 此库文件放在项目文件夹内.
constexpr const char * BMP_FILE = "/bmp.bmp"; //在此更改你的图片文件路径名
constexpr const char * JPG_FILE = "/jpg.jpg"; //在此更改你的图片文件路径名
constexpr const char * PNG_FILE = "/png.png"; //在此更改你的图片文件路径名
ReadguyDriver guy;//新建一个readguy对象, 用于显示驱动.
void setup(){
// 1 - 初始化和启动ReadGuy --------------------- 1 - 初始化和启动ReadGuy -------<
Serial.begin(115200); //初始化串口
guy.init(); //初始化readguy_driver 基础驱动库.
guy.setRotation(3); //设置旋转方向. 因为此库的功能不随旋转而发生变化.
// 2 - 显示图片 方式1 --------------------- 2 - 显示图片 方式1:使用readguy提供的drawImage方法. ------<<
//此功能可以在 画布{1}(Sprite) 绘制图片, 再使用内置的drawImage方法来显示出这个sprite.
//此方法消耗的内存颇多, 需要为整个sprite分配所有内存才能成功绘制.
//由于目前版本LovyanGFX的限制, 不能分配4bit的画布, 只能分配大小至少为8bit的画布.
guy.fillScreen(1); //清屏
//guy.display(); //显示白屏,用于将来显示图片.
LGFX_Sprite sp(&guy); //创建一个 画布(Sprite) (可以存储一些像素, 快速读写)
sp.setColorDepth(lgfx::v1::color_depth_t::grayscale_8bit); //设置画布sprite的色彩深度
sp.createSprite(80,80); //为刚刚创建的画布对象分配内存 (像素宽度*像素高度). 此处为80(宽)*80(高)像素
sp.drawBmpFile(guy.guyFS(),BMP_FILE,0,0); //从文件创建BMP图像信息. 文件系统内必须要有这个BMP图像.
//默认的文件系统为SD卡. 当没有插入SD卡时, 会读取LittleFS文件系统.
//没有条件准备文件系统的, 可以烧录文件系统.
Serial.printf("[%lu] sp.w: %d, h: %d, res: %im.\n",millis(),sp.width(),sp.height(),ESP.getFreeHeap());
guy.drawImage(sp,9,11); //使用抖动像素的方式显示图片(不是灰度, 只有黑点和白点的那种显示效果)
//sp为要显示的画布, 后面的9 11都是要显示的目标坐标位置. 此处代表将画布显示在 屏幕缓存{2} 的第9列, 第11行.
Serial.printf("[%lu] drawn dithering bmp.\n",millis()); //显示信息
guy.display(); //1.2.0版本之后, drawImage不再刷屏, 需要额外调用display函数刷屏.
delay(2000);
guy.display(FILL_WHITE,true); //在保持屏幕缓存不变的时候快速刷新白屏.
guy.drawImage(sp,0,0,guy.width(),guy.height()); //绘画的画布可以被放大或者缩小到任意宽度和高度.
//此处的参数调用表示将会在屏幕坐标(0,0)开始显示, 显示的画布宽度缩放到屏幕宽度, 画布高度缩放到屏幕高度.
guy.display(); //调用display函数刷屏.
delay(2000);
guy.display(FILL_WHITE,true); //在保持屏幕缓存不变的时候快速刷新白屏
guy.fillScreen(1); //白屏清屏(清屏幕缓存)
guy.drawImage(sp,10,10,65,50); //缩放: 缩小到65X50
guy.display(); //调用display函数刷屏.
delay(2000);
//以下函数可以允许你用灰度显示画布.
//在刷灰度之前, 该函数可以对刷屏区域自动慢刷白屏.
//0-关闭连续刷屏 开启16阶灰度抖动. 连续刷屏并非所有的屏幕都支持. 所以此处使用了一个函数检验.
if(guy.supportGreyscaling()==-16){ //此函数返回-16代表支持连续刷屏. 返回16代表支持灰度, 返回0代表仅黑白.
guy.fillScreen(1); //白屏清屏(清屏幕缓存)
guy.setGreyQuality(0); //设置刷屏方式:0-关闭连续刷屏, 开启16阶灰度抖动.
guy.draw16grey(sp,9,9); //使用16级灰度的方式显示图片 需要的时间比较长
delay(2000);
}
//1-开启连续刷屏 开启16阶灰度抖动 (默认值)
guy.fillScreen(1); //白屏清屏(清屏幕缓存)
guy.setGreyQuality(1); //设置刷屏方式:1-开启连续刷屏, 开启16阶灰度抖动. 在初始化时, 这是默认值.
guy.draw16grey(sp,12,12,120,120); //使用16级灰度的方式显示图片. 后两个参数120代表缩放之后的宽度高度
delay(2000);
//2-关闭连续刷屏 关闭16阶灰度抖动. 连续刷屏并非所有的屏幕都支持. 所以此处使用了一个函数检验.
if(guy.supportGreyscaling()==-16){ //此函数返回-16代表支持连续刷屏. 返回16代表支持灰度, 返回0代表仅黑白.
guy.fillScreen(1); //白屏清屏(清屏幕缓存)
guy.setGreyQuality(2); //2-关闭连续刷屏, 关闭16阶灰度抖动
guy.draw16grey(sp,9,9,50,100); //使用16级灰度的方式显示图片. 参数50和100分别代表宽度缩放和高度缩放
delay(2000);
}
//3-开启连续刷屏 关闭16阶灰度抖动
guy.fillScreen(1); //白屏清屏(清屏幕缓存)
guy.setGreyQuality(3); //3-打开连续刷屏, 关闭16阶灰度抖动
guy.draw16grey(sp,3,3,100,50); //使用16级灰度的方式显示图片 参数100和50分别代表宽度缩放和高度缩放
delay(2000);
//灰度显示画布到此显示完了
sp.deleteSprite(); //关闭画布, 释放图片占用的大量内存
Serial.printf("[%lu] drawn 16-layer greyscale bmp.\n",millis()); //显示信息
delay(2000);
// 3 - 显示图片 方式2 --------------------- 3 - 显示图片 方式2:使用readguyImage类. -----<<<
//readguy驱动程序的内部有一个屏幕缓存, 可以用于存储显示引擎显示的内容, 在刷屏时 直接发送此缓存的内容.
//显示引擎会通过显示函数, 来改写屏幕缓存的内容. (显示就是这样显示的. 类似于print, drawPixel, fillRect...)
//此绘制方式可以做到几乎不消耗额外的内存, 仅需要屏幕缓存即可显示图片.
//适合用在esp8266或者消耗的内存较多的项目上.
//显示原理比较复杂, 因为显示函数的限制, 图片不能直接显示到屏幕缓存中.
//大体上说就是用屏幕缓存作为显示图片的中间内存, 再分阶段解码图片, 最后通过自定义刷屏函数, 完成刷新图片
//使用此方法, 因为屏幕缓存被复用作图片解码中间内存, 屏幕缓存上原本的内容会因为图片解码而发生改变.
//不需要指定额外的外部内存.
//显示BMP格式
readguyImage im(guy); //定义一个绘制器, 此类中的函数用于绘制图片.
//所有的绘制图片的参数均需要放入此结构内
//直接更改im内的数据即可设置绘制参数. (就像结构体一样用它)
im.baseFs=&guy.guyFS(); //在此处就是设置文件系统.
im.filename=BMP_FILE; //在此直接设置文件路径和文件名.
guy.display(FILL_WHITE,false); //将屏幕全刷成白屏. 为了即将的图片刷新.
//建议在使用drawImageFile函数之前, 使用慢刷刷白屏, 可以保证显示效果清晰可见.
im.drawImageFile(); //显示BMP格式.图片. im会自动识别文件扩展名并绘制.
//此处不再需要调用 guy.display() 函数即可显示.
//此时显示缓存已经清空(因为显示缓存内原本是用来解码图片用的)
//此时直接使用guy.display()将刷白屏.
delay(2000);
//显示JPG格式
im.filename=JPG_FILE; //在此直接设置文件路径和文件名.
im.scalex=0.3333333f; //设置横坐标图片缩放
im.scaley=0.3333333f; //设置纵坐标图片缩放.
im.offsetx=70; //设置显示偏移. 此处的70说明 JPG图像应该从图片文件的(70,40)坐标处开始解码
im.offsety=40; //设置显示偏移Y轴方向.
im.background=0; //设置背景颜色, 0黑1白, 此处设为背景色为黑色.
guy.display(FILL_WHITE,false); //将屏幕全刷成白屏. 为了即将的图片刷新.
im.drawImageFile(); //显示JPG格式.图片. im会自动识别文件扩展名并绘制.
delay(2000);
#ifndef ESP8266 //显示PNG格式. ESP8266不支持PNG格式.
im.filename=PNG_FILE; //PNG图片. ESP8266的内存不够, 所以不能显示PNG格式~(T_T)~
im.x=10; //设置在哪里绘制图片. 在此编辑图片绘制起始点的 X, Y 坐标
im.y=5 ; //
im.scalex=400.0f/1280.0f;
im.scaley=300.0f/576.0f;
guy.display(FILL_WHITE, false);//显示. 此处的功能就是将显示缓存输出到屏幕上
im.drawImageFile(); //显示PNG格式.图片. ESP8266可能不会绘制.
delay(2000);
#endif
//开启灰度显示, 然后显示BMP格式
im.filename=BMP_FILE; //在此直接设置文件路径和文件名.
im.scalex=0.75f; //设置横坐标图片缩放
im.scaley=0.5625f; //设置纵坐标图片缩放.
im.offsetx=0; //设置显示偏移. 此处的70说明 JPG图像应该从图片文件的(70,40)坐标处开始解码
im.offsety=0; //设置显示偏移Y轴方向.
im.background=1; //设置背景颜色, 0黑1白, 此处设为背景色为黑色.
//预分配内存, 可以用作显示缓存.
#ifdef ESP8266
#define MEM_POOL 10000
#else
#define MEM_POOL 60000
#endif
uint8_t *buf = new uint8_t [MEM_POOL]; //分配缓存
im.exPool=buf; //设置外部缓存内存地址
im.exPoolSize=MEM_POOL; //设置外部缓存内存大小
guy.setGreyQuality(1); //设置灰度模式为默认灰度显示模式
guy.display(FILL_WHITE,false); //将屏幕全刷成白屏. 为了即将显示灰度图.
im.enableFloyd=0; //禁用掉抖动算法.
im.drawImageFile(1); //显示JPG格式.图片.
delay(2000);
guy.display(FILL_WHITE,false); //将屏幕全刷成白屏. 为了即将显示灰度图.
im.enableFloyd=1; // 重新启用抖动算法.
im.drawImageFile(1); //显示JPG格式.图片.
delay(5000);
delete [] buf; //缓存用完了, 释放内存.
im.exPool=nullptr; //用完之后将这个内存地址置为null, 避免重复使用导致内存不可访问.
im.exPoolSize=0;
// --------------------- 4 - 显示图片 使用readguyImage类的externalBuffer绘制到主屏幕缓存. -----<<<
//实现原理: 通过一个额外的缓存, 显示引擎可以分多次将图片显示在这个额外的内存中, 再将这块内存写入到缓存内.
//由于用了外部内存, 所以可以分多次显示一张大图片, 相比于分配整张大图片的内存, 要更节省内存.代价是速度更慢
//readguy驱动程序的内部有一个屏幕缓存, 如果使用这个缓存来显示图片, 屏幕上原本的东西也会变得混乱.(上个示例)
//因此, readguyImage类提供了使用外部内存的显示功能.
//分为多次, 每次将图片的一小部分显示到外部内存中, 再写入到屏幕缓存内.
//需要提供额外分配好的内存, 但此函数不会更改显示区域之外的显示缓存.
guy.fillScreen(1); //将屏幕全刷成白屏. 为了即将的图片刷新.
for(int i=1;i<10;i++){ //在屏幕上绘画线段. 本质是更改显示缓存.
guy.drawLine(i*guy.width()/10,0,0,i*guy.height()/10,0);
guy.drawLine(i*guy.width()/10,guy.height()-1,guy.width()-1,i*guy.height()/10,0);
}
guy.drawLine(guy.width(),0,0,guy.height(),0);
guy.display(false); //刷新屏幕, 显示绘画的线段
//im.baseFs=&guy.guyFS(); //直接更改im内的数据即可设置绘制参数. 在此处就是设置文件系统.
im.filename=BMP_FILE; //在此直接设置文件路径和文件名.
im.background=0; //设置背景颜色, 0黑1白, 此处设为背景色为黑色.
im.x=15; //设置将要显示的横坐标
im.y=15; //设置将要显示的纵坐标
im.w=180; //设置将要显示区域的宽度
im.h=270; //设置将要显示区域的高度
im.offsetx=0; //如果不从左上角开始解码图片, 从哪个坐标开始显示/解码图片?
im.offsety=0;
im.scalex=0.5625f; //设置横坐标缩放.
im.scaley=0.5625f; //设置纵坐标缩放. 为0代表与横坐标缩放相同.
buf = new uint8_t [ 60*270 ]; //分配缓存
im.exPool=buf; //设置外部缓存内存地址
im.exPoolSize=60*270; //设置外部缓存内存大小
im.drawImageToBuffer(); //将图片写入到屏幕缓存. im会自动识别文件扩展名并绘制.
guy.display(); //显示缓存.此时应当能看到图片.
delete [] buf; //缓存用完了, 释放内存.
im.exPool=nullptr; //用完之后将这个内存地址置为null, 避免重复使用导致内存不可访问.
im.exPoolSize=0;
}
void loop(){
delay(1);
}/* END OF FILE. ReadGuy project.
Copyright (C) 2023 FriendshipEnder. */

View File

@@ -80,9 +80,11 @@ uint8_t readguyImage::drawImgHandler(int r, LGFX_Sprite *spr){
case 1:
spr->drawBmpFile(*baseFs,filename,_x,_y,0,0,offsetx+xd,offsety+yd,scalex,scaley,datum);
break;
#ifndef ESP8266
case 2:
spr->drawPngFile(*baseFs,filename,_x,_y,0,0,offsetx+xd,offsety+yd,scalex,scaley,datum);
break;
#endif
case 3:
spr->drawJpgFile(*baseFs,filename,_x,_y,0,0,offsetx+xd,offsety+yd,scalex,scaley,datum);
break;
@@ -224,9 +226,9 @@ void readguyImage::drawImageFile(bool use16grey){
//Serial.printf("filename: %s, exname: %s\n",filename,ex);
//图片将会分割成8个部分, 分块绘制, 节省内存.
w=(guy->memWidth()+7)&0x7ffffff8; //guy->guyMemoryWidth() 返回不随旋转参数而改变的显示内存宽度
w=(guy->drvWidth()+7)&0x7ffffff8; //guy->guyMemoryWidth() 返回不随旋转参数而改变的显示内存宽度
if(!w) return; //保证宽度>0
h=guy->memHeight();
h=guy->drvHeight();
if(exPoolSize>guy->bufferLength()){ //当外部缓存的像素超过屏幕缓存时,使用外部缓存作为主缓冲区
_h=exPoolSize/w;
_pool=exPool;
@@ -250,8 +252,10 @@ void readguyImage::drawImageFile(bool use16grey){
//随后打开图片进行解码. 可选显示的位置和宽度高度参数, 屏幕上的其他部分则会变成白色.
if(strcmp(ex,"bmp") == 0 || strcmp(ex,"BMP") == 0) //BMP格式, 绘制BMP图片
format|=1; //BMP格式
#ifndef ESP8266
else if(strcmp(ex,"png") == 0 || strcmp(ex,"PNG") == 0) //PNG格式, 绘制PNG图片
format|=2; //PNG格式
#endif
else if(strcmp(ex,"jpg") == 0 || strcmp(ex,"JPG") == 0 || strcmp(ex,"jpeg") == 0 || strcmp(ex,"JPEG") == 0)
format|=3; //JPG格式
else return; //未知格式
@@ -291,8 +295,10 @@ uint8_t readguyImage::drawImageToBuffer(){
if(strcmp(ex,"bmp") == 0 || strcmp(ex,"BMP") == 0) //BMP格式, 绘制BMP图片
format|=1; //BMP格式
#ifndef ESP8266
else if(strcmp(ex,"png") == 0 || strcmp(ex,"PNG") == 0) //PNG格式, 绘制PNG图片
format|=2; //PNG格式
#endif
else if(strcmp(ex,"jpg") == 0 || strcmp(ex,"JPG") == 0 || strcmp(ex,"jpeg") == 0 || strcmp(ex,"JPEG") == 0)
format|=3; //JPG格式
else return 2; //未知格式
@@ -310,9 +316,11 @@ uint8_t readguyImage::drawImageToBuffer(){
case 1:
spr.drawBmpFile(*baseFs,filename,0,0,0,0,offsetx,offsety+_h*i,scalex,scaley);
break;
#ifndef ESP8266
case 2:
spr.drawPngFile(*baseFs,filename,0,0,0,0,offsetx,offsety+_h*i,scalex,scaley);
break;
#endif
case 3:
spr.drawJpgFile(*baseFs,filename,0,0,0,0,offsetx,offsety+_h*i,scalex,scaley);
break;

View File

@@ -1,341 +0,0 @@
/******************** F r i e n d s h i p E n d e r ********************
* 本程序隶属于 Readguy 开源项目, 请尊重开源开发者, 也就是我FriendshipEnder.
* 如果有条件请到 extra/artset/reward 中扫描打赏,否则请在 Bilibili 上支持我.
* 项目交流QQ群: 926824162 (萌新可以进来问问题的哟)
* 郑重声明: 未经授权还请不要商用本开源项目编译出的程序.
*
* @file ex06_Image.ino
* @author FriendshipEnder (f_ender@163.com), Bilibili: FriendshipEnder
* @version 1.0
* @date 2023-11-01
* @brief ReadGuy 图片显示功能演示.
* 1. 在运行过ex01或者ex02的开发板上 编译烧录本程序.
* 2. 将该项目data文件夹内的所有文件放置于SD卡的根目录上.
*
* @note 重要消息: 这是一个实验性功能. 可能你所使用的LGFX库版本较旧而无法通过编译.
* 如果你的项目中无法成功编译源码中的setBuffer, 请更改LovyanGFX库的函数!
* 位于文件 LovyanGFX/src/lgfx/v1/LGFX_Sprite.hpp
* 第155行 void setBuffer 函数:
* 修改为如下内容并保存
*
``` C++
void setBuffer(void* buffer, int32_t w, int32_t h, color_depth_t bpp = rgb565_2Byte)
{
deleteSprite();
if (bpp != 0) {
_write_conv.setColorDepth(bpp);
_read_conv = _write_conv;
_panel_sprite.setColorDepth(bpp);
}
_panel_sprite.setBuffer(buffer, w, h, &_write_conv);
_img = _panel_sprite.getBuffer();
_sw = w;
_clip_r = w - 1;
_xpivot = w >> 1;
_sh = h;
_clip_b = h - 1;
_ypivot = h >> 1;
}
```
* 完成后请再次尝试编译
* [已经向lovyan03/LovyanGFX发布issue, 等待解决]
*
* @attention
* Copyright (c) 2022-2023 FriendshipEnder
*
* Apache License, Version 2.0
*
* 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 <Arduino.h> //arduino功能基础库. 在platformIO平台上此语句不可或缺
#include "readguy.h" //包含readguy_driver 基础驱动库
#include "guy_image.h" //包含 readguy_image 图片功能库. 此库文件放在项目文件夹内.
constexpr const char * BMP_FILE = "/bmp.bmp"; //在此更改你的图片文件路径名
constexpr const char * JPG_FILE = "/jpg.jpg"; //在此更改你的图片文件路径名
constexpr const char * PNG_FILE = "/png.png"; //在此更改你的图片文件路径名
ReadguyDriver guy;//新建一个readguy对象, 用于显示驱动.
void setup(){
// 1 - 初始化和启动ReadGuy --------------------- 1 - 初始化和启动ReadGuy -------<
Serial.begin(115200); //初始化串口
guy.init(); //初始化readguy_driver 基础驱动库.
guy.setRotation(3); //设置旋转. 旋转属性不会影响图片显示的所有功能
// 2 - 显示图片 方式1 --------------------- 2 - 显示图片 方式1:使用readguy提供的drawImage方法. ------<<
//此功能可以在Sprite绘制图片, 再使用内置的drawImage方法来显示出这个sprite.
//此方法消耗的内存颇多, 需要整个sprite才能成功绘制.
guy.fillScreen(1); //清屏
guy.display(); //显示白屏,用于将来显示图片.
LGFX_Sprite sp(&guy); //创建一个Sprite (可以存储一些像素, 快速读写)
sp.setColorDepth(lgfx::v1::color_depth_t::grayscale_8bit); //设置sprite的色彩深度
sp.createSprite(80,80); //从文件创建BMP图像信息.
sp.drawBmpFile(guy.guyFS(),BMP_FILE,0,0); //显示JPG文件
Serial.printf("[%lu] sp.w: %d, h: %d, res: %im.\n",millis(),sp.width(),sp.height(),ESP.getFreeHeap());
guy.drawImage(sp,9,9); //使用抖动像素的方式显示图片(不是灰度, 只有黑点和白点的那种显示效果)
Serial.printf("[%lu] drawn dithering bmp.\n",millis()); //显示信息
guy.display(); //1.2.0版本之后, drawImage不再刷屏, 需要额外调用display函数刷屏.
delay(2000);
//0-关闭连续刷屏 开启16阶灰度抖动
guy.setGreyQuality(0); //部分屏幕允许使用不同的灰度显示模式.
guy.draw16grey(sp,9,9); //使用16级灰度的方式显示图片 需要的时间比较长
delay(2000);
//1-开启连续刷屏 开启16阶灰度抖动 (默认值)
guy.setGreyQuality(1); //部分屏幕允许使用不同的灰度显示模式.
guy.draw16grey(sp,9,9); //使用16级灰度的方式显示图片 需要的时间比较长
delay(2000);
//2-关闭连续刷屏 关闭16阶灰度抖动
guy.setGreyQuality(2); //部分屏幕允许使用不同的灰度显示模式.
guy.draw16grey(sp,9,9); //使用16级灰度的方式显示图片 需要的时间比较长
delay(2000);
//3-开启连续刷屏 关闭16阶灰度抖动
guy.setGreyQuality(3); //部分屏幕允许使用不同的灰度显示模式.
guy.draw16grey(sp,9,9); //使用16级灰度的方式显示图片 需要的时间比较长
delay(2000);
sp.deleteSprite(); //关闭图片文件, 释放图片占用的大量内存
Serial.printf("[%lu] drawn 16-layer greyscale bmp.\n",millis()); //显示信息
delay(2000);
// 3 - 显示图片 方式2 --------------------- 3 - 显示图片 方式2:使用readguyImage类. -----<<<
//readguy驱动程序的内部有一个屏幕缓存, 可以用于存储显示引擎显示的内容, 在刷屏时 直接发送此缓存的内容.
//显示引擎会通过显示函数, 来改写屏幕缓存的内容. (显示就是这样显示的. 类似于print, drawPixel, fillRect...)
//此绘制方式可以做到几乎不消耗额外的内存, 仅需要屏幕缓存即可显示图片.
//适合用在esp8266或者消耗的内存较多的项目上.
//显示原理比较复杂, 因为显示函数的限制, 图片不能直接显示到屏幕缓存中.
//大体上说就是用屏幕缓存作为显示图片的中间内存, 再分阶段解码图片, 最后通过自定义刷屏函数, 完成刷新图片
//使用此方法, 因为屏幕缓存被复用作图片解码中间内存, 屏幕缓存上原本的内容会因为图片解码而发生改变.
//不需要指定额外的外部内存.
//显示BMP格式
readguyImage im(guy); //定义一个绘制器, 此类中的函数用于绘制图片.
//所有的绘制图片的参数均需要放入此结构内
//直接更改im内的数据即可设置绘制参数. (就像结构体一样用它)
im.baseFs=&guy.guyFS(); //在此处就是设置文件系统.
im.filename=BMP_FILE; //在此直接设置文件路径和文件名.
guy.display(FILL_WHITE,false); //将屏幕全刷成白屏. 为了即将的图片刷新.
//建议在使用drawImageFile函数之前, 使用慢刷刷白屏, 可以保证显示效果清晰可见.
im.drawImageFile(); //显示BMP格式.图片. im会自动识别文件扩展名并绘制.
//此处不再需要调用 guy.display() 函数即可显示.
//此时显示缓存已经清空(因为显示缓存内原本是用来解码图片用的)
//此时直接使用guy.display()将刷白屏.
delay(2000);
//显示JPG格式
im.filename=JPG_FILE; //在此直接设置文件路径和文件名.
im.scalex=0.3333333f; //设置横坐标图片缩放
im.scaley=0.3333333f; //设置纵坐标图片缩放.
im.offsetx=70; //设置显示偏移. 此处的70说明 JPG图像应该从图片文件的(70,40)坐标处开始解码
im.offsety=40; //设置显示偏移Y轴方向.
im.background=0; //设置背景颜色, 0黑1白, 此处设为背景色为黑色.
guy.display(FILL_WHITE,false); //将屏幕全刷成白屏. 为了即将的图片刷新.
im.drawImageFile(); //显示JPG格式.图片. im会自动识别文件扩展名并绘制.
delay(2000);
#ifndef ESP8266 //显示PNG格式. ESP8266不支持PNG格式
im.filename=PNG_FILE; //PNG图片. ESP8266的内存不够, 所以不能显示PNG格式~(T_T)~
im.x=10; //设置在哪里绘制图片. 在此编辑图片绘制起始点的 X, Y 坐标
im.y=5 ; //
im.scalex=400.0f/1280.0f;
im.scaley=300.0f/576.0f;
guy.display(FILL_WHITE, false); //显示. 此处的功能就是将显示缓存输出到屏幕上
im.drawImageFile(); //显示JPG格式.图片. ESP8266可能不会绘制.
delay(2000);
#endif
//开启灰度显示, 然后显示BMP格式
im.filename=BMP_FILE; //在此直接设置文件路径和文件名.
im.scalex=0.75f; //设置横坐标图片缩放
im.scaley=0.5625f; //设置纵坐标图片缩放.
im.offsetx=0; //设置显示偏移. 此处的70说明 JPG图像应该从图片文件的(70,40)坐标处开始解码
im.offsety=0; //设置显示偏移Y轴方向.
im.background=1; //设置背景颜色, 0黑1白, 此处设为背景色为黑色.
#ifdef ESP8266
#define MEM_POOL 10000
#else
#define MEM_POOL 60000
#endif
uint8_t *buffer = new uint8_t [ MEM_POOL ]; //分配缓存
im.exPool=buffer; //设置外部缓存内存地址
im.exPoolSize=MEM_POOL; //设置外部缓存内存大小
guy.setGreyQuality(1); //设置灰度模式为默认灰度显示模式
guy.display(FILL_WHITE,false); //将屏幕全刷成白屏. 为了即将显示灰度图.
im.enableFloyd=0; //禁用掉抖动算法.
im.drawImageFile(1); //显示JPG格式.图片.
delay(2000);
guy.display(FILL_WHITE,false); //将屏幕全刷成白屏. 为了即将显示灰度图.
im.enableFloyd=1; // 重新启用抖动算法.
im.drawImageFile(1); //显示JPG格式.图片.
delay(5000);
delete []buffer;
im.exPool=nullptr; //用完之后将这个内存地址置为null, 避免重复使用导致内存不可访问.
im.exPoolSize=0;
/*
// 灰度测试代码 -------------------------------------------------------------------------- 灰度测试代码
im.filename=BMP_FILE; //在此直接设置文件路径和文件名.
guy.setGreyQuality(0);
guy.display(FILL_WHITE, false);
#ifdef ESP8266
#define MEM_POOL 10000
#else
#define MEM_POOL 60000
#endif
uint8_t *buffer = new uint8_t [ MEM_POOL ]; //分配缓存
im.exPool=buffer; //设置外部缓存内存地址
im.exPoolSize=MEM_POOL; //设置外部缓存内存大小
im.enableFloyd = 0;
im.drawImageFile(true); //显示BMP格式.图片. im会自动识别文件扩展名并绘制.
//此处不再需要调用 guy.display() 函数即可显示.
//此时显示缓存已经清空(因为显示缓存内原本是用来解码图片用的)
//此时直接使用guy.display()将刷白屏.
delay(3000);
guy.display(FILL_WHITE,false);
im.enableFloyd = 1;
guy.setGreyQuality(1);
im.drawImageFile(true); //显示BMP格式.图片. im会自动识别文件扩展名并绘制.
delay(3000);
//显示JPG格式
im.filename=JPG_FILE; //在此直接设置文件路径和文件名.
im.scalex=1.3333333f;
im.scaley=1.3333333f;
//im.scalex=0.3333333f; //设置横坐标图片缩放
//im.scaley=0.3333333f; //设置纵坐标图片缩放.
//im.offsetx=70; //设置显示偏移. 此处的70说明 JPG图像应该从图片文件的(70,40)坐标处开始解码
//im.offsety=40; //设置显示偏移Y轴方向.
im.background=0; //设置背景颜色, 0黑1白, 此处设为背景色为黑色.
guy.display(FILL_WHITE, false);
im.drawImageFile(); //显示JPG格式.图片. im会自动识别文件扩展名并绘制.
delete []buffer;
// 灰度测试代码 -------------------------------------------------------------------------- 灰度测试代码
*/
// --------------------- 4 - 显示图片 使用readguyImage类的externalBuffer绘制到主屏幕缓存. -----<<<
//实现原理: 通过一个额外的缓存, 显示引擎可以分多次将图片显示在这个额外的内存中, 再将这块内存写入到缓存内.
//由于用了外部内存, 所以可以分多次显示一张大图片, 相比于分配整张大图片的内存, 要更节省内存.代价是速度更慢
//readguy驱动程序的内部有一个屏幕缓存, 如果使用这个缓存来显示图片, 屏幕上原本的东西也会变得混乱.(上个示例)
//因此, readguyImage类提供了使用外部内存的显示功能.
//分为多次, 每次将图片的一小部分显示到外部内存中, 再写入到屏幕缓存内.
//需要提供额外分配好的内存, 但此函数不会更改显示区域之外的显示缓存.
guy.fillScreen(1); //将屏幕全刷成白屏. 为了即将的图片刷新.
for(int i=1;i<10;i++){ //在屏幕上绘画线段. 本质是更改显示缓存.
guy.drawLine(i*guy.width()/10,0,0,i*guy.height()/10,0);
guy.drawLine(i*guy.width()/10,guy.height()-1,guy.width()-1,i*guy.height()/10,0);
}
guy.drawLine(guy.width(),0,0,guy.height(),0);
guy.display(false); //刷新屏幕, 显示绘画的线段
//im.baseFs=&guy.guyFS(); //直接更改im内的数据即可设置绘制参数. 在此处就是设置文件系统.
im.filename=BMP_FILE; //在此直接设置文件路径和文件名.
im.background=0; //设置背景颜色, 0黑1白, 此处设为背景色为黑色.
im.x=15; //设置将要显示的横坐标
im.y=15; //设置将要显示的纵坐标
im.w=180; //设置将要显示区域的宽度
im.h=270; //设置将要显示区域的高度
im.offsetx=0; //如果不从左上角开始解码图片, 从哪个坐标开始显示/解码图片?
im.offsety=0;
im.scalex=0.5625f; //设置横坐标缩放.
im.scaley=0.5625f; //设置纵坐标缩放. 为0代表与横坐标缩放相同.
buffer = new uint8_t [ 60*270 ]; //分配缓存
im.exPool=buffer; //设置外部缓存内存地址
im.exPoolSize=60*270; //设置外部缓存内存大小
im.drawImageToBuffer(); //将图片写入到屏幕缓存. im会自动识别文件扩展名并绘制.
guy.display(); //显示缓存.此时应当能看到图片.
delete []buffer;
}
void loop(){
delay(1);
}/* END OF FILE. ReadGuy project.
Copyright (C) 2023 FriendshipEnder. */