├── .gitignore ├── package.json ├── LICENSE ├── README.md └── src ├── create.js └── extract.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.png 3 | *.bin -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pocket-image-process", 3 | "version": "1.0.0", 4 | "description": "Prepares and extract bitmaps for Analogue Pocket platform images", 5 | "main": "index.js", 6 | "scripts": { 7 | "create": "node src/create.js", 8 | "extract": "node src/extract.js" 9 | }, 10 | "type": "module", 11 | "author": "", 12 | "license": "MIT", 13 | "dependencies": { 14 | "image-encode": "^1.3.1", 15 | "image-pixels": "^2.2.2" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Adam Gastineau 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Analogue Pocket Framework Image Processor 2 | 3 | Simple image processing for APF platform images. 4 | 5 | ![Analogue platform image demo](https://images.analogue.co/platform_art.2ad219560f99a334bf59ef9641c433f6.png?auto=format&w=1200&q=100&s=74e9636d11c828a74b67f9a060a59abb) 6 | 7 | ## Creation 8 | 9 | The creation script converts a 521x165 PNG image into a 16 bit greyscale bitmap, where the upper byte stores the brightness of the pixel, where white is `0x00` and black is `0xFF`. At the moment, the lower byte is always `0x00`. This image is stored rotated 90 degrees counter-clockwise (creating a resolution of 165x521). 10 | 11 | To prepare an image for display on the Pocket: 12 | 13 | 1. Create a 521x165 greyscale image (transparent pixels will be converted into white) 14 | 2. Save image as PNG 15 | 3. Run: 16 | 17 | ```bash 18 | npm run create input.png .bin 19 | ``` 20 | 21 | 4. Place this image in the appropriate platform folder in the `/Platforms/_images/`. [See the Analogue docs for more information](https://www.analogue.co/developer/docs/packaging-a-core#graphical-asset-formats) 22 | 23 | ## Extraction 24 | 25 | Extracting an existing platform image will invert the color (back to what it started as) and revert the 90 degree rotation performed during creation. 26 | 27 | To extract a previously created APF platform image, run: 28 | 29 | ```bash 30 | npm run extract input.bin output.png 31 | ``` 32 | 33 | -------------------------------------------------------------------------------- /src/create.js: -------------------------------------------------------------------------------- 1 | import { writeFileSync } from "fs"; 2 | import pixels from "image-pixels"; 3 | import { argv } from "process"; 4 | 5 | if (argv.length !== 4) { 6 | console.log(`Received ${argv.length - 2} arguments. Expected 2\n`); 7 | console.log("Usage: node create.js [input.png] [output.bin]"); 8 | 9 | process.exit(1); 10 | } 11 | 12 | const inputFile = argv[2]; 13 | const outputFile = argv[3]; 14 | 15 | const create = async () => { 16 | let { data, width, height } = await pixels(inputFile); 17 | 18 | let image = new Array(data.length / 2); 19 | 20 | for (let i = 0; i < data.length; i += 4) { 21 | // Only read red pixel and invert color 22 | let byte = 255 - data[i]; 23 | 24 | if (data[i + 3] == 0) { 25 | // If alpha is 0, write black 26 | byte = 0; 27 | } 28 | 29 | // Brightness byte 30 | image[i / 2] = byte; 31 | // Reserved byte 32 | image[i / 2 + 1] = 0; 33 | } 34 | 35 | let rotatedImage = Buffer.alloc(image.length); 36 | 37 | for (let i = 0; i < image.length / 2; i++) { 38 | const byte1 = image[i * 2]; 39 | const byte2 = image[i * 2 + 1]; 40 | 41 | const prevColumn = i % width; 42 | const prevRow = Math.floor(i / width); 43 | 44 | const row = width - 1 - prevColumn; 45 | const column = prevRow; 46 | 47 | rotatedImage[(row * height + column) * 2] = byte1; 48 | rotatedImage[(row * height + column) * 2 + 1] = byte2; 49 | } 50 | 51 | writeFileSync(outputFile, rotatedImage); 52 | }; 53 | 54 | create(); 55 | -------------------------------------------------------------------------------- /src/extract.js: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync } from "fs"; 2 | import encode from "image-encode"; 3 | import { argv } from "process"; 4 | 5 | if (argv.length !== 4) { 6 | console.log(`Received ${argv.length - 2} arguments. Expected 2\n`); 7 | console.log("Usage: node extract.js [input.bin] [output.png]"); 8 | 9 | process.exit(1); 10 | } 11 | 12 | const inputFile = argv[2]; 13 | const outputFile = argv[3]; 14 | 15 | const buffer = readFileSync(inputFile); 16 | 17 | let rgba = new Array((buffer.length / 2) * 4); 18 | 19 | let j = 0; 20 | for (let i = 0; i < buffer.length; i += 2) { 21 | const datum = 255 - buffer[i]; 22 | rgba[j] = datum; 23 | rgba[j + 1] = datum; 24 | rgba[j + 2] = datum; 25 | // Alpha 26 | rgba[j + 3] = 255; 27 | 28 | j += 4; 29 | } 30 | 31 | const width = 165; 32 | const height = 521; 33 | 34 | let rotatedImage = Buffer.alloc(rgba.length); 35 | 36 | for (let i = 0; i < rgba.length / 4; i++) { 37 | const byte1 = rgba[i * 4]; 38 | const byte2 = rgba[i * 4 + 1]; 39 | const byte3 = rgba[i * 4 + 2]; 40 | const byte4 = rgba[i * 4 + 3]; 41 | 42 | const prevColumn = i % width; 43 | const prevRow = Math.floor(i / width); 44 | 45 | const row = prevColumn; 46 | const column = height - 1 - prevRow; 47 | 48 | const outputPos = (row * height + column) * 4; 49 | rotatedImage[outputPos] = byte1; 50 | rotatedImage[outputPos + 1] = byte2; 51 | rotatedImage[outputPos + 2] = byte3; 52 | rotatedImage[outputPos + 3] = byte4; 53 | } 54 | 55 | const png = encode(rotatedImage, "png", [height, width]); 56 | 57 | writeFileSync(outputFile, Buffer.from(png)); 58 | --------------------------------------------------------------------------------