diff --git a/.gitignore b/.gitignore index a1fc39c..840577e 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,8 @@ gradle-app.setting # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 # gradle/wrapper/gradle-wrapper.properties + +.idea/ +cache +error.log +gradle/ \ No newline at end of file diff --git a/README.en.md b/README.en.md deleted file mode 100644 index adb73a5..0000000 --- a/README.en.md +++ /dev/null @@ -1,36 +0,0 @@ -# ink-photo-album - -#### Description -用Java编写的基于树莓派的电子墨水屏相册 - -#### Software Architecture -Software architecture description - -#### Installation - -1. xxxx -2. xxxx -3. xxxx - -#### Instructions - -1. xxxx -2. xxxx -3. xxxx - -#### Contribution - -1. Fork the repository -2. Create Feat_xxx branch -3. Commit your code -4. Create Pull Request - - -#### Gitee Feature - -1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md -2. Gitee blog [blog.gitee.com](https://blog.gitee.com) -3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) -4. The most valuable open source project [GVP](https://gitee.com/gvp) -5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) -6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..32c2c87 --- /dev/null +++ b/build.gradle @@ -0,0 +1,37 @@ +plugins { + id 'java-library' + id 'com.github.johnrengelman.shadow' version '6.1.0' +} + + +group 'icu.namophice' +version '1.0-SNAPSHOT' +description = 'slow' +sourceCompatibility = JavaVersion.VERSION_11 + +repositories { + mavenLocal() + mavenCentral() + maven { + url namophiceNexusPublic + allowInsecureProtocol true + } +} + +dependencies { + implementation group: 'com.alibaba', name: 'fastjson', version: '1.2.76' + // RaspbarryPi驱动接口核心包 + implementation group: 'com.pi4j', name: 'pi4j-core', version: '1.4' +} + +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' +} + +jar { + manifest { + attributes( + 'Main-Class': 'icu.namophice.inkphotoalbum.Main' + ) + } +} diff --git a/conf.json b/conf.json new file mode 100644 index 0000000..44041a1 --- /dev/null +++ b/conf.json @@ -0,0 +1,4 @@ +{ + "enable_image_fill": true, // 是否开启图片填充 + "local_images_mode": false // 本地图片库模式 +} \ No newline at end of file diff --git a/images/self_1.jpg b/images/self_1.jpg new file mode 100644 index 0000000..5f169c8 Binary files /dev/null and b/images/self_1.jpg differ diff --git a/images/self_10.jpg b/images/self_10.jpg new file mode 100644 index 0000000..81f8b1f Binary files /dev/null and b/images/self_10.jpg differ diff --git a/images/self_13.jpg b/images/self_13.jpg new file mode 100644 index 0000000..5096d31 Binary files /dev/null and b/images/self_13.jpg differ diff --git a/images/self_15.jpg b/images/self_15.jpg new file mode 100644 index 0000000..2db5bec Binary files /dev/null and b/images/self_15.jpg differ diff --git a/images/self_16.jpg b/images/self_16.jpg new file mode 100644 index 0000000..19749e4 Binary files /dev/null and b/images/self_16.jpg differ diff --git a/images/self_17.jpg b/images/self_17.jpg new file mode 100644 index 0000000..de632ad Binary files /dev/null and b/images/self_17.jpg differ diff --git a/images/self_2.jpg b/images/self_2.jpg new file mode 100644 index 0000000..80faddf Binary files /dev/null and b/images/self_2.jpg differ diff --git a/images/self_22.jpg b/images/self_22.jpg new file mode 100644 index 0000000..e676fd4 Binary files /dev/null and b/images/self_22.jpg differ diff --git a/images/self_6.jpg b/images/self_6.jpg new file mode 100644 index 0000000..f1976db Binary files /dev/null and b/images/self_6.jpg differ diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..08ebb9d --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'ink-photo-album' + diff --git a/src/main/java/icu/namophice/inkphotoalbum/Main.java b/src/main/java/icu/namophice/inkphotoalbum/Main.java new file mode 100644 index 0000000..6bfd29e --- /dev/null +++ b/src/main/java/icu/namophice/inkphotoalbum/Main.java @@ -0,0 +1,39 @@ +package icu.namophice.inkphotoalbum; + +import icu.namophice.inkphotoalbum.business.MasterService; +import icu.namophice.inkphotoalbum.config.DefaultConfig; +import icu.namophice.inkphotoalbum.utils.CommonUtil; + +/** + * @author Namophice + * @createTime 2021-07-29 16:42 + */ +public class Main { + + public static void main(String [] args) { + try { + CommonUtil.printLogToConsole("Application Started ..."); + + // 初始化配置 + DefaultConfig.initConfig(); + + // 从cache文件获取imageIndex + DefaultConfig.initImageIndexWithCache(); + + // 进入主流程 + MasterService.master(); + } catch (Exception e) { + CommonUtil.printErrorToLogFile(e); + } finally { + try { + // 打印下次获取的图片下标位置到cache文件 + CommonUtil.printImageIndexToCache(DefaultConfig.imageIndex); + } catch (Exception e) { + CommonUtil.printErrorToLogFile(e); + } + + CommonUtil.printLogToConsole("Application Exit !"); + } + } + +} diff --git a/src/main/java/icu/namophice/inkphotoalbum/business/MasterService.java b/src/main/java/icu/namophice/inkphotoalbum/business/MasterService.java new file mode 100644 index 0000000..6ff9f2d --- /dev/null +++ b/src/main/java/icu/namophice/inkphotoalbum/business/MasterService.java @@ -0,0 +1,154 @@ +package icu.namophice.inkphotoalbum.business; + +import icu.namophice.inkphotoalbum.config.DefaultConfig; +import icu.namophice.inkphotoalbum.driver.EPaper; +import icu.namophice.inkphotoalbum.utils.CommonUtil; +import icu.namophice.inkphotoalbum.utils.ImageUtil; + +import java.awt.image.BufferedImage; +import java.io.*; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author Namophice + * @createTime 2021-08-05 10:27 + */ +public class MasterService { + + public static void master() throws IOException, InterruptedException { + CommonUtil.printLogToConsole("The master program is running ..."); + + if (DefaultConfig.imageIndex < 0) { + DefaultConfig.imageIndex = 0; + } + + EPaper ePaper = EPaper.getInstance(); + ePaper.init(); + + if (DefaultConfig.imageIndex == 0) { + BufferedImage defaultImage = ImageUtil.getImageToScreen(); + + CommonUtil.printLogToConsole("Print images to screen ..."); + ePaper.drawImage(defaultImage); + } + + DefaultConfig.defaultImageIsLoaded = true; + + if (DefaultConfig.local_images_mode()) { // local + CommonUtil.printLogToConsole("Enabled local images mode ..."); + + CommonUtil.printLogToConsole("Find images directory ..."); + File imagesDir = new File(CommonUtil.rootPath + "/" + DefaultConfig.imagePath); + + if (imagesDir.exists()) { + if (imagesDir.isDirectory()) { + CommonUtil.printLogToConsole("Images directory is exists ..."); + + CommonUtil.printLogToConsole("Find images files ..."); + File[] imageList = imagesDir.listFiles(); + imagesDir = null; + + if (imageList == null || imageList.length < 1) { + DefaultConfig.imageIndex = 0; + return; + } + + imageList = Arrays.stream(imageList).filter(imageFile -> !imageFile.isDirectory()).toArray(File[]::new); + if (imageList.length < 1) { + DefaultConfig.imageIndex = 0; + return; + } + + if (DefaultConfig.imageIndex < imageList.length) { + BufferedImage targetImage = ImageUtil.getImageToScreen(imageList[DefaultConfig.imageIndex]); + + CommonUtil.printLogToConsole("Print images to screen ..."); + ePaper.drawImage(targetImage); + + DefaultConfig.imageIndex++; + } else { + DefaultConfig.imageIndex = 0; + } + } + } + } else { // remote + CommonUtil.printLogToConsole("Enabled remote images mode ..."); + + if (!(DefaultConfig.imageIndex < 20)) { + DefaultConfig.imageIndex = 0; + } + + StringBuffer htmlStr = new StringBuffer(); + + InputStream inputStream = null; + InputStreamReader inputStreamReader = null; + BufferedReader bufferedReader = null; + + try { + URL url = new URL("https://wall.alphacoders.com/popular.php?page=" + (DefaultConfig.imageIndex + 1)); + inputStream = url.openStream(); + inputStreamReader = new InputStreamReader(inputStream, "utf-8"); + bufferedReader = new BufferedReader(inputStreamReader); + + String line; + while ((line = bufferedReader.readLine()) != null) { + htmlStr.append(line).append("\n"); + } + + bufferedReader.close(); + } catch (IOException e) { + DefaultConfig.imageIndex = 0; + throw e; + }finally { + try { + if(bufferedReader!=null){ + bufferedReader.close(); + bufferedReader=null; + } + if(inputStreamReader!=null){ + inputStreamReader.close(); + inputStreamReader=null; + } + if(inputStream!=null){ + inputStream.close(); + inputStream=null; + } + } catch (IOException e) { + throw e; + } + } + + if (htmlStr.length() > 0) { + final List imageUrlList = new ArrayList<>(); + + final String IMGURL_REG = "(https://images(.*)thumbbig-(\\d*).jpg)"; + Matcher matcher = Pattern.compile(IMGURL_REG).matcher(htmlStr); + while (matcher.find()){ + imageUrlList.add(matcher.group()); + } + + if (imageUrlList.size() < 1) { + DefaultConfig.imageIndex = 0; + return; + } + + String imageUrl = imageUrlList.get(new Random().nextInt(imageUrlList.size())); + imageUrl = imageUrl.replace("/thumbbig-", "/"); + + BufferedImage targetImage = ImageUtil.getImageToScreen(imageUrl, true); + + CommonUtil.printLogToConsole("Print images to screen ..."); + ePaper.drawImage(targetImage); + + DefaultConfig.imageIndex++; + } + } + } + +} diff --git a/src/main/java/icu/namophice/inkphotoalbum/config/DefaultConfig.java b/src/main/java/icu/namophice/inkphotoalbum/config/DefaultConfig.java new file mode 100644 index 0000000..de895bd --- /dev/null +++ b/src/main/java/icu/namophice/inkphotoalbum/config/DefaultConfig.java @@ -0,0 +1,135 @@ +package icu.namophice.inkphotoalbum.config; + +import com.alibaba.fastjson.JSONObject; +import icu.namophice.inkphotoalbum.utils.CommonUtil; + +import java.io.*; + +/** + * @author Namophice + * @createTime 2021-08-04 11:46 + */ +public class DefaultConfig { + + public static final String configFileName = "conf.json"; + public static final String cacheFileName = "cache"; + + public static final String imagePath = "images"; + + public static int imageIndex; + + /** + * 默认图片是否被加载过 + */ + public static boolean defaultImageIsLoaded = false; + + /** + * 是否开启图片填充 + */ + private static boolean enable_image_fill = false; + public static final boolean enable_image_fill() { + return enable_image_fill; + } + + /** + * 本地图片库模式 + */ + private static boolean local_images_mode = true; + public static boolean local_images_mode() { + return local_images_mode; + } + + /** + * 初始化配置文件 + */ + public static void initConfig() { + CommonUtil.printLogToConsole("Init config ..."); + + File configFile = new File(CommonUtil.rootPath + "/" + configFileName); + if (configFile.exists()) { + + BufferedReader bufferedReader = null; + final StringBuilder confJsonStrBuilder = new StringBuilder(); + + try { + bufferedReader = new BufferedReader(new FileReader(configFile)); + + String lineStr; + + while ((lineStr = bufferedReader.readLine()) != null) { + confJsonStrBuilder.append(lineStr).append("\n"); + } + + bufferedReader.close(); + } catch (Exception e) { + CommonUtil.printErrorToLogFile(e); + } finally { + if (bufferedReader != null) { + try { + bufferedReader.close(); + } catch (Exception e) { + CommonUtil.printErrorToLogFile(e); + } + } + } + + final String confJsonStr = confJsonStrBuilder.toString(); + if (confJsonStr.length() > 2 && confJsonStr.contains("{") && confJsonStr.contains("}")) { + JSONObject conJsonMap = JSONObject.parseObject(confJsonStr); + if (conJsonMap.size() > 0) { + if (conJsonMap.get("enable_image_fill") != null) { + final Boolean cst_enable_image_fill = conJsonMap.getBoolean("enable_image_fill"); + if (cst_enable_image_fill != null) { + enable_image_fill = cst_enable_image_fill; + } + } + if (conJsonMap.get("local_images_mode") != null) { + final Boolean cst_local_images_mode = conJsonMap.getBoolean("local_images_mode"); + if (cst_local_images_mode != null) { + local_images_mode = cst_local_images_mode; + } + } + } + } + } + } + + /** + * 从cache文件获取imageIndex + * @throws IOException + */ + public static void initImageIndexWithCache() throws IOException { + CommonUtil.printLogToConsole("Init image index with cache ..."); + + DefaultConfig.imageIndex = 0; + + File cacheFile = new File(CommonUtil.rootPath + "/" + cacheFileName); + if (cacheFile.exists() && !cacheFile.isDirectory()) { + BufferedReader bufferedReader = null; + String lineStr = null; + + try { + bufferedReader = new BufferedReader(new FileReader(cacheFile)); + + lineStr = bufferedReader.readLine().trim(); + + bufferedReader.close(); + } catch (Exception e) { + CommonUtil.printErrorToLogFile(e); + } finally { + if (bufferedReader != null) { + try { + bufferedReader.close(); + } catch (Exception e) { + CommonUtil.printErrorToLogFile(e); + } + } + } + + if (lineStr.length() > 0) { + DefaultConfig.imageIndex = Integer.parseInt(lineStr); + } + } + } + +} diff --git a/src/main/java/icu/namophice/inkphotoalbum/driver/EPaper.java b/src/main/java/icu/namophice/inkphotoalbum/driver/EPaper.java new file mode 100644 index 0000000..3362782 --- /dev/null +++ b/src/main/java/icu/namophice/inkphotoalbum/driver/EPaper.java @@ -0,0 +1,342 @@ +package icu.namophice.inkphotoalbum.driver; + +import com.pi4j.io.gpio.*; +import com.pi4j.io.spi.SpiChannel; +import com.pi4j.io.spi.SpiDevice; +import com.pi4j.io.spi.SpiFactory; +import icu.namophice.inkphotoalbum.utils.CommonUtil; + +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferByte; +import java.io.IOException; + +/** + * @author Namophice + * @createTime 2021-07-29 17:00 + */ +public class EPaper { + + public static final int width = 800; + public static final int height = 480; + public static final long resolution = width * height; + + private EPaper() {} + + private static EPaper ePaper; + + public static EPaper getInstance() { + return ePaper = new EPaper(); + } + + private static GpioPinDigitalOutput CS; + private static GpioPinDigitalOutput DC; + private static GpioPinDigitalOutput RST; + private static GpioPinDigitalInput BUSY; + + public static SpiDevice spiDevice; + + static { + RaspiGpioProvider raspiGpioProvider = new RaspiGpioProvider(RaspiPinNumberingScheme.BROADCOM_PIN_NUMBERING); + + final GpioController gpio = GpioFactory.getInstance(); + + CS = gpio.provisionDigitalOutputPin(raspiGpioProvider, RaspiBcmPin.GPIO_08, "CS", PinState.HIGH); + DC = gpio.provisionDigitalOutputPin(raspiGpioProvider, RaspiBcmPin.GPIO_25, "DC", PinState.HIGH); + RST = gpio.provisionDigitalOutputPin(raspiGpioProvider, RaspiBcmPin.GPIO_17, "RST", PinState.HIGH); + BUSY = gpio.provisionDigitalInputPin(raspiGpioProvider, RaspiBcmPin.GPIO_24, "BUSY"); + + try { + spiDevice = SpiFactory.getInstance(SpiChannel.CS1, SpiDevice.DEFAULT_SPI_SPEED, SpiDevice.DEFAULT_SPI_MODE); + } catch (Exception e) { + CommonUtil.printErrorToLogFile(e); + } + } + + /** + * 绘制图片到屏幕 + * @param image + * @throws IOException + * @throws InterruptedException + */ + public void drawImage(BufferedImage image) throws IOException, InterruptedException { + this.initLut(); + this.clear(); + this.display(image); + } + + /** + * 重置屏幕 + * @throws InterruptedException + */ + public void reset() throws InterruptedException { + CommonUtil.printLogToConsole("Reset the screen ..."); + + RST.high(); + Thread.sleep(200); + RST.low(); + Thread.sleep(10); + RST.high(); + Thread.sleep(200); + } + + /** + * 写入指令 + * @param data + * @throws IOException + */ + public void sendCommand(int data) throws IOException { + DC.low(); + CS.low(); + spiDevice.write((byte) data); + CS.high(); + } + + /** + * 写入数据 + * @param data + * @throws IOException + */ + public void sendData(int data) throws IOException { + DC.high(); + CS.low(); + spiDevice.write((byte) data); + CS.high(); + } + + /** + * 检查屏幕是否Busy + * @throws IOException + * @throws InterruptedException + */ + public void checkBusy() throws IOException, InterruptedException { + CommonUtil.printLogToConsole("Check whether the screen is busy ..."); + + this.sendCommand((byte) 0x71); + while (BUSY.isLow()) { + this.sendCommand((byte) 0x71); + Thread.sleep(200); + } + } + + public void init() throws IOException, InterruptedException { + CommonUtil.printLogToConsole("Init the driver ..."); + + reset(); + + this.sendCommand(0x01); // power setting + this.sendData(0x17); // 1-0=11: internal power + this.sendData(Voltage_Frame_7IN5_V2[6]); // VGH&VGL + this.sendData(Voltage_Frame_7IN5_V2[1]); // VSH + this.sendData(Voltage_Frame_7IN5_V2[2]); // VSL + this.sendData(Voltage_Frame_7IN5_V2[3]); // VSHR + + this.sendCommand(0x82); // # VCOM DC Setting + this.sendData(Voltage_Frame_7IN5_V2[4]); // VCOM + + this.sendCommand(0x06); // Booster Setting + this.sendData(0x27); + this.sendData(0x27); + this.sendData(0x2F); + this.sendData(0x17); + + this.sendCommand(0x30); // OSC Setting + this.sendData(Voltage_Frame_7IN5_V2[0]); // 2-0=100: N=4 5-3=111: M=7 3C=50Hz 3A=100HZ + + this.sendCommand(0x04); // power on + this.checkBusy(); + + this.sendCommand(0X00); // PANNEL SETTING + this.sendData(0x3F); // #KW-3f KWR-2F BWROTP 0f BWOTP 1f + + this.sendCommand(0x61); // tres + this.sendData(0x03); // source 800 + this.sendData(0x20); + this.sendData(0x01); // gate 480 + this.sendData(0xE0); + + this.sendCommand(0X15); + this.sendData(0x00); + + this.sendCommand(0X50); // VCOM AND DATA INTERVAL SETTING + this.sendData(0x10); + this.sendData(0x07); + + this.sendCommand(0X60); // TCON SETTING + this.sendData(0x22); + + this.sendCommand(0x65); // Resolution setting + this.sendData(0x00); + this.sendData(0x00); // 800*480 + this.sendData(0x00); + this.sendData(0x00); + } + + /** + * 初始化LUT + * @throws IOException + */ + public void initLut() throws IOException { + CommonUtil.printLogToConsole("Set LUT ..."); + + this.sendCommand(0x20); + for (int data : LUT_VCOM_7IN5_V2) { + this.sendData(data); + } + + this.sendCommand(0x21); + for (int data : LUT_WW_7IN5_V2) { + this.sendData(data); + } + + this.sendCommand(0x22); + for (int data : LUT_BW_7IN5_V2) { + this.sendData(data); + } + + this.sendCommand(0x23); + for (int data : LUT_WB_7IN5_V2) { + this.sendData(data); + } + + this.sendCommand(0x24); + for (int data : LUT_BB_7IN5_V2) { + this.sendData(data); + } + } + + /** + * 清屏 + * @throws IOException + * @throws InterruptedException + */ + public void clear() throws IOException, InterruptedException { + CommonUtil.printLogToConsole("Clear screen ..."); + + this.initLut(); + + this.sendCommand(0x10); + + for (int i = 0; i < resolution; i++) { + this.sendData(0xFF); + } + + this.sendCommand(0x13); + + for (int i = 0; i < resolution; i++) { + this.sendData(0xFF); + } + + this.sendCommand(0x12); + + this.checkBusy(); + } + + /** + * 绘制屏幕画面 + * @param image + * @throws IOException + * @throws InterruptedException + */ + public void display(BufferedImage image) throws IOException, InterruptedException { + final byte[] pixels = ((DataBufferByte)(image.getRaster().getDataBuffer())).getData(); + this.display(pixels); + } + + /** + * 绘制屏幕画面 + * @param pixels + * @throws IOException + * @throws InterruptedException + */ + public void display(byte[] pixels) throws IOException, InterruptedException { + CommonUtil.printLogToConsole("Print image to screen ..."); + + this.initLut(); + + this.sendCommand(0x13); + + int step = 0; + int temp = 0; + + for (int i = 0; i < 800 * 480; i++) { + int grayNum = pixels[i] & 0xFF; + if (grayNum < GRAY_SCALE_OF_4[0]) { + temp |= 1; + } else if(grayNum < GRAY_SCALE_OF_4[1]){ + temp |= 0; + }else if (grayNum < GRAY_SCALE_OF_4[2]) { + temp |= 1; + } else if(grayNum <= GRAY_SCALE_OF_4[3]){ + temp |= 0; + } + + step++; + if (step != 8) { + temp = temp << 1; + } else { + step = 0; + this.sendData(temp); + temp = 0; + } + } + + this.sendCommand(0x12); + + this.checkBusy(); + } + + private final static int[] Voltage_Frame_7IN5_V2 = { 0x6, 0x3F, 0x3F, 0x11, 0x24, 0x7, 0x17 }; + + private static final int[] GRAY_SCALE_OF_4 = {0xff / 4, 0xff / 4 * 2, 0xff / 4 * 3, 0xff}; + + private final static int[] LUT_VCOM_7IN5_V2 = { + 0x0, 0xF, 0xF, 0x0, 0x0, 0x1, + 0x0, 0xF, 0x1, 0xF, 0x1, 0x2, + 0x0, 0xF, 0xF, 0x0, 0x0, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 + }; + + private final static int[] LUT_WW_7IN5_V2 = { + 0x10, 0xF, 0xF, 0x0, 0x0, 0x1, + 0x84, 0xF, 0x1, 0xF, 0x1, 0x2, + 0x20, 0xF, 0xF, 0x0, 0x0, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 + }; + + private final static int[] LUT_BW_7IN5_V2 = { + 0x10, 0xF, 0xF, 0x0, 0x0, 0x1, + 0x84, 0xF, 0x1, 0xF, 0x1, 0x2, + 0x20, 0xF, 0xF, 0x0, 0x0, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 + }; + + private final static int[] LUT_WB_7IN5_V2 = { + 0x80, 0xF, 0xF, 0x0, 0x0, 0x1, + 0x84, 0xF, 0x1, 0xF, 0x1, 0x2, + 0x40, 0xF, 0xF, 0x0, 0x0, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 + }; + + private final static int[] LUT_BB_7IN5_V2 = { + 0x80, 0xF, 0xF, 0x0, 0x0, 0x1, + 0x84, 0xF, 0x1, 0xF, 0x1, 0x2, + 0x40, 0xF, 0xF, 0x0, 0x0, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 + }; + +} diff --git a/src/main/java/icu/namophice/inkphotoalbum/utils/CommonUtil.java b/src/main/java/icu/namophice/inkphotoalbum/utils/CommonUtil.java new file mode 100644 index 0000000..b7b3900 --- /dev/null +++ b/src/main/java/icu/namophice/inkphotoalbum/utils/CommonUtil.java @@ -0,0 +1,97 @@ +package icu.namophice.inkphotoalbum.utils; + +import icu.namophice.inkphotoalbum.config.DefaultConfig; + +import java.io.*; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * @author Namophice + * @createTime 2021-08-01 17:44 + */ +public class CommonUtil { + + /** + * 获取程序运行根目录 + * @return + */ + public static String rootPath = System.getProperty("user.dir"); + + /** + * 打印异常信息至error.log + * @param exception + */ + public static void printErrorToLogFile(Exception exception) { + printErrorToLogFile(exception, true); + } + + /** + * 打印异常信息至error.log + * @param exception + * @param clearLogFile + */ + public static void printErrorToLogFile(Exception exception, boolean clearLogFile) { + if (exception != null) { + try { + exception.printStackTrace(); + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + + exception.printStackTrace(pw); + final String errorDetail = "Error [" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:ms").format(LocalDateTime.now()) + "] " + sw; + + sw.close(); + pw.close(); + + File logFile = new File(rootPath + "/error.log"); + if ((!logFile.exists()) || logFile.isDirectory()) { + logFile.createNewFile(); + } + + FileWriter fileWriter = new FileWriter(logFile, clearLogFile); + fileWriter.write(errorDetail); + fileWriter.flush(); + fileWriter.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + /** + * 获取日志时间头 + * @return + */ + public static String getLogTime() { + return "[" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:ms").format(LocalDateTime.now()) + "] "; + } + + /** + * 打印日志到控制台 + * @param log + */ + public static void printLogToConsole(final String log) { + System.out.println("Info " + getLogTime() + log); + } + + /** + * 打印图片下标位置到cache文件 + * @param imageIndex + * @throws IOException + */ + public static void printImageIndexToCache(int imageIndex) throws IOException { + CommonUtil.printLogToConsole("Print image index to cache file ..."); + + File cacheFile = new File(CommonUtil.rootPath + "/" + DefaultConfig.cacheFileName); + if ((!cacheFile.exists()) || cacheFile.isDirectory()) { + cacheFile.createNewFile(); + } + FileWriter fileWriter = new FileWriter(cacheFile, false); + fileWriter.write(String.valueOf(imageIndex)); + fileWriter.flush(); + fileWriter.close(); + } + +} diff --git a/src/main/java/icu/namophice/inkphotoalbum/utils/ImageUtil.java b/src/main/java/icu/namophice/inkphotoalbum/utils/ImageUtil.java new file mode 100644 index 0000000..aea7fa0 --- /dev/null +++ b/src/main/java/icu/namophice/inkphotoalbum/utils/ImageUtil.java @@ -0,0 +1,344 @@ +package icu.namophice.inkphotoalbum.utils; + +import icu.namophice.inkphotoalbum.config.DefaultConfig; +import icu.namophice.inkphotoalbum.driver.EPaper; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.net.URL; +import java.util.Random; + +/** + * @author Namophice + * @createTime 2021-08-04 16:28 + */ +public class ImageUtil { + + /** + * 获取可直接打印至屏幕的Image流 + * @param imagePath + * @param isRemoteUrl + * @return + * @throws IOException + */ + public static BufferedImage getImageToScreen(final String imagePath, final boolean isRemoteUrl) throws IOException { + BufferedImage targetImage = getImage(imagePath, isRemoteUrl); + targetImage = tailoringImage(targetImage, EPaper.width, EPaper.height, DefaultConfig.enable_image_fill()); + targetImage = getGaryImg(targetImage); + + return targetImage; + } + + /** + * 获取可直接打印至屏幕的Image流 + * @param imageFile + * @return + * @throws IOException + */ + public static BufferedImage getImageToScreen(File imageFile) throws IOException { + BufferedImage targetImage = getImage(imageFile); + targetImage = tailoringImage(targetImage, EPaper.width, EPaper.height, DefaultConfig.enable_image_fill()); + targetImage = getGaryImg(targetImage); + + return targetImage; + } + + /** + * 获取可直接打印至屏幕的Image流 + * @return + * @throws IOException + */ + public static BufferedImage getImageToScreen() throws IOException { + return getImageToScreen(null); + } + + /** + * 获取图片流 + * @param imageUrl + * @param isRemoteUrl + * @return + * @throws IOException + */ + public static BufferedImage getImage(final String imageUrl, final boolean isRemoteUrl) throws IOException { + CommonUtil.printLogToConsole("Input image to BufferedImage ..."); + + if (imageUrl == null || imageUrl.length() < 1) { + return getDefaultImage(); + } else { + InputStream imageStream; + + if (isRemoteUrl) { + CommonUtil.printLogToConsole("Image path: " + imageUrl + " ..."); + + URL url = new URL(imageUrl); + imageStream = url.openStream(); + } else { + File imgFile = new File(imageUrl); + if (imgFile.exists()) { + CommonUtil.printLogToConsole("Image path: " + imgFile.getAbsolutePath() + " ..."); + + imageStream = new FileInputStream(imgFile); + } else { + return getDefaultImage(); + } + } + + BufferedImage originImage = ImageIO.read(imageStream); + imageStream.close(); + imageStream = null; + + BufferedImage targetImage = new BufferedImage(originImage.getWidth(), originImage.getHeight(), BufferedImage.TYPE_BYTE_GRAY); + for (int i = 0; i < originImage.getWidth(); i++) { + for (int j = 0; j < originImage.getHeight(); j++) { + targetImage.setRGB(i, j, originImage.getRGB(i, j)); + } + } + + return targetImage; + } + } + + /** + * 获取图片流 + * @param imageFile + * @return + * @throws IOException + */ + public static BufferedImage getImage(File imageFile) throws IOException { + CommonUtil.printLogToConsole("Input image to BufferedImage ..."); + + if (imageFile == null || !imageFile.exists() || imageFile.isDirectory()) { + return getDefaultImage(); + } else { + CommonUtil.printLogToConsole("Image path: " + imageFile.getAbsolutePath() + " ..."); + + InputStream imageStream = new FileInputStream(imageFile); + BufferedImage originImage = ImageIO.read(imageStream); + imageStream.close(); + + BufferedImage targetImage = new BufferedImage(originImage.getWidth(), originImage.getHeight(), BufferedImage.TYPE_BYTE_GRAY); + for (int i = 0; i < originImage.getWidth(); i++) { + for (int j = 0; j < originImage.getHeight(); j++) { + targetImage.setRGB(i, j, originImage.getRGB(i, j)); + } + } + + return targetImage; + } + } + + /** + * 获取默认图片流 + * @return + * @throws IOException + */ + public static BufferedImage getDefaultImage() throws IOException { + CommonUtil.printLogToConsole("Input default image to BufferedImage ..."); + + InputStream defaultImageStream = CommonUtil.class.getClassLoader().getResourceAsStream("images/default.bmp"); + BufferedImage originImage = ImageIO.read(defaultImageStream); + defaultImageStream.close(); + defaultImageStream = null; + + BufferedImage targetImage = new BufferedImage(originImage.getWidth(), originImage.getHeight(), BufferedImage.TYPE_BYTE_GRAY); + for (int i = 0; i < originImage.getWidth(); i++) { + for (int j = 0; j < originImage.getHeight(); j++) { + targetImage.setRGB(i, j, originImage.getRGB(i, j)); + } + } + + return targetImage; + } + + /** + * 对图片做自适应or填充处理(默认自适应) + * @param originImage + * @param screenWidth + * @param screenHeight + * @param enable_image_fill + * @return + */ + public static BufferedImage tailoringImage(BufferedImage originImage, final int screenWidth, final int screenHeight, final boolean enable_image_fill) { + CommonUtil.printLogToConsole("Tailoring image with screen ..."); + + // 获取原始图片宽高 + final BigDecimal originWidth = new BigDecimal(originImage.getWidth()); + final BigDecimal originHeight = new BigDecimal(originImage.getHeight()); + + BufferedImage targetImage; + + if (originWidth.intValue() == screenWidth && originHeight.intValue() == screenHeight) { // 图片宽高 == 屏幕宽高 + return originImage; + } else if (originWidth.intValue() < screenWidth && originHeight.intValue() < screenHeight) { // 图片宽高 皆小于 屏幕宽高 + targetImage = new BufferedImage(screenWidth, screenHeight, BufferedImage.TYPE_INT_RGB); + + // 将原图居中,计算原图在输出图片中的位置 + final int xStart = (screenWidth - originWidth.intValue()) / 2; + final int xEnd = originWidth.intValue() + xStart; + final int yStart = (screenHeight - originHeight.intValue()) / 2; + final int yEnd = originHeight.intValue() + yStart; + + for (int x = 0; x < screenWidth; x++) { + for (int y = 0; y < screenHeight; y++) { + // 在输出图片中未被原始图片覆盖的位置直接做涂黑处理 + if (x < xStart || x >= xEnd) { + targetImage.setRGB(x, y, 0); + } else { + if (y < yStart || y >= yEnd) { + targetImage.setRGB(x, y, 0); + } else { + targetImage.setRGB(x, y, originImage.getRGB(x - xStart, y - yStart)); + } + } + } + } + + return targetImage; + } else { // 图片宽 > 屏幕宽 or 图片高 > 屏幕高 + // 屏幕宽高比例 + final BigDecimal screenPercentage = new BigDecimal(screenWidth).divide(new BigDecimal(screenHeight), 5, RoundingMode.HALF_DOWN); + // 原图宽高比例 + final BigDecimal imagePercentage = originWidth.divide(originHeight, 5, RoundingMode.HALF_UP); + + + int percentageCompareRes = screenPercentage.compareTo(imagePercentage); + if (percentageCompareRes == 0) { // 图片宽高比例 == 屏幕宽高比例 + return originImage; + } else { + // 等比例放大,相同高度下,屏幕宽度小于原图宽度 + boolean check = originHeight.multiply(new BigDecimal(screenWidth)).compareTo(originWidth.multiply(new BigDecimal(screenHeight))) < 1; + + if (!DefaultConfig.defaultImageIsLoaded || !enable_image_fill) { // 自适应模式 + CommonUtil.printLogToConsole("Enabled image adaptive mode ..."); + + if (check) { + final int targetWight = originWidth.intValue(); + final int targetHeight = originWidth.divide(screenPercentage, 0, RoundingMode.HALF_DOWN).intValue(); + + targetImage = new BufferedImage(targetWight, targetHeight, BufferedImage.TYPE_INT_RGB); + + final int yStart = (targetHeight - originHeight.intValue()) / 2; + final int yEnd = originHeight.intValue() + yStart; + + for (int x = 0; x < targetWight; x++) { + for (int y = 0; y < targetHeight; y++) { + if (yStart <= y && y < yEnd) { + targetImage.setRGB(x, y, originImage.getRGB(x, y - yStart)); + } else { + targetImage.setRGB(x, y, 0); + } + } + } + } else { + final int targetWight = originHeight.multiply(screenPercentage).intValue(); + final int targetHeight = originHeight.intValue(); + + targetImage = new BufferedImage(targetWight, targetHeight, BufferedImage.TYPE_INT_RGB); + + final int xStart = (targetWight - originWidth.intValue()) / 2; + final int xEnd = originWidth.intValue() + xStart; + + for (int x = 0; x < targetWight; x++) { + for (int y = 0; y < targetHeight; y++) { + if (xStart <= x && x < xEnd) { + targetImage.setRGB(x, y, originImage.getRGB(x - xStart, y)); + } else { + targetImage.setRGB(x, y, 0); + } + } + } + } + } else { // 填充模式 + CommonUtil.printLogToConsole("Enabled image fill mode ..."); + + if (check) { + final int targetWight = originHeight.multiply(screenPercentage).intValue(); + final int targetHeight = originHeight.intValue(); + + targetImage = new BufferedImage(targetWight, targetHeight, BufferedImage.TYPE_INT_RGB); + + final int xStart = originWidth.subtract(new BigDecimal(targetWight)).divide(new BigDecimal(2), 0, RoundingMode.HALF_UP).intValue(); + final int xEnd = originWidth.intValue() - xStart; + + for (int x = 0; x < originWidth.intValue(); x++) { + for (int y = 0; y < originHeight.intValue(); y++) { + if (xStart <= x && x < xEnd) { + targetImage.setRGB(x - xStart, y, originImage.getRGB(x, y)); + } + } + } + } else { + final int targetWight = originWidth.intValue(); + final int targetHeight = originWidth.divide(screenPercentage, 0, RoundingMode.HALF_UP).intValue(); + + targetImage = new BufferedImage(targetWight, targetHeight, BufferedImage.TYPE_INT_RGB); + + final int yStart = originHeight.subtract(new BigDecimal(targetHeight)).divide(new BigDecimal(2), 0, RoundingMode.HALF_UP).intValue(); + final int yEnd = originHeight.intValue() - yStart; + + for (int x = 0; x < originWidth.intValue(); x++) { + for (int y = 0; y < originHeight.intValue(); y++) { + if (yStart <= y && y < yEnd) { + targetImage.setRGB(x, y - yStart, originImage.getRGB(x, y)); + } + } + } + } + } + + // 根据屏幕做等比例缩放 + CommonUtil.printLogToConsole("Scale image to the screen ..."); + BufferedImage finallyImage = new BufferedImage(EPaper.width, EPaper.height, BufferedImage.TYPE_BYTE_GRAY); + Graphics graphics = finallyImage.getGraphics(); + graphics.drawImage(targetImage, 0, 0, EPaper.width, EPaper.height, null); + + return finallyImage; + } + } + } + + /** + * 获取灰度图 + * @param originImage + * @return + */ + public static BufferedImage getGaryImg(BufferedImage originImage) { + CommonUtil.printLogToConsole("Convert RGB to GRAY ..."); + + // 获取原始图片宽高 + final int width = originImage.getWidth(); + final int height = originImage.getHeight(); + + BufferedImage targetImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY); + for (int i = 0; i < width; i++) { + for (int j = 0; j < height; j++) { + int rgb = originImage.getRGB(i, j); + int oneGate = rgb & 0xffffff; + int randomNum = new Random().nextInt(0xffffff); + int binValue; + + //0是黑 1是白 ,或者说数值小就靠近黑色,数值大就靠近白色 + if(oneGate > 0xec82e0) { // 大于一定数值,直接用白点 + binValue = 0xffffff; + } else if(oneGate < 0x6ea050) { // 小于一定数值直接用黑点 + binValue = 0; + } else if(oneGate > randomNum) { // 模拟灰阶使用随机数画白点 + binValue = 0xffffff; + } else { + binValue = 0; + } + targetImage.setRGB(i, j, binValue); + } + } + + return targetImage; + } + +} diff --git a/src/main/resources/images/default.bmp b/src/main/resources/images/default.bmp new file mode 100644 index 0000000..25ce18f Binary files /dev/null and b/src/main/resources/images/default.bmp differ diff --git a/src/test/java/icu/namophice/inkphotoalbum/Test.java b/src/test/java/icu/namophice/inkphotoalbum/Test.java new file mode 100644 index 0000000..956d9e6 --- /dev/null +++ b/src/test/java/icu/namophice/inkphotoalbum/Test.java @@ -0,0 +1,33 @@ +package icu.namophice.inkphotoalbum; + +import icu.namophice.inkphotoalbum.config.DefaultConfig; +import icu.namophice.inkphotoalbum.utils.CommonUtil; +import icu.namophice.inkphotoalbum.utils.ImageUtil; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +/** + * @author Namophice + * @createTime 2021-07-29 16:43 + */ +public class Test { + + public static void main(String[] args) { + try { + BufferedImage targetImage = ImageUtil.getImageToScreen(CommonUtil.rootPath + "/" + DefaultConfig.imagePath + "/" + "self_22.jpg", false); + printImage(targetImage, CommonUtil.rootPath + "/test.jpg"); + } catch (Exception e) { + e.printStackTrace(); + CommonUtil.printErrorToLogFile(e); + } + } + + private static void printImage(BufferedImage image, String path) throws IOException { + File file = new File(path); + ImageIO.write(image, "JPG", file); + } + +} diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..a54b47e --- /dev/null +++ b/start.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +/usr/bin/rm /opt/software/ink-photo-album/cache + +while true +do + cd /opt/software/ink-photo-album + /usr/bin/sudo /usr/bin/java -jar /opt/software/ink-photo-album/ink-photo-album-1.0-SNAPSHOT-all.jar + sleep 5m +done \ No newline at end of file