ai-nevermore 0.0.5 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,14 +29,19 @@ Install with `npm install -g ai-nevermore`
29
29
  nevermore [command]
30
30
 
31
31
  Commands:
32
- nevermore pseudotext [input-file] process the input
32
+ nevermore pseudotext [input-file] transform text to poison
33
+ nevermore pseudoimage [input-file] transform XOR image encoding
33
34
 
34
35
  Options:
35
36
  --version Show version number [boolean]
37
+ -K, --key key to use for decoding the image [string]
36
38
  -U, --unified-output File to generate html + css into [string]
37
39
  -C, --css-output File to generate css into [string]
40
+ -I, --image-output File to output image to [string]
38
41
  -H, --html-output File to generate html into [string]
39
42
  -r, --raw-output Do not wrap the ouput [boolean]
43
+ -E, --encode inline encoding [boolean]
44
+ -D, --decode inline decoding [boolean]
40
45
  -m, --render-mode output mode
41
46
  [string] [choices: "fixed-mode", "inline-mode"] [default: "inline-mode"]
42
47
  -s, --size The size of the font in pixels (required for
@@ -58,6 +63,8 @@ Roadmap
58
63
  - [x] raw output mode
59
64
  - [x] stdin, stdout support
60
65
  - [x] custom dictionary
66
+ - [x] image encoding
67
+ - [ ] web component decoder
61
68
  - [ ] self randomizing dictionary
62
69
  - [ ] add a replacement mode (opposed to a tokenizer based solution)
63
70
 
package/bin/nevermore CHANGED
@@ -2,9 +2,12 @@
2
2
  import yargs from 'yargs';
3
3
  import { hideBin } from 'yargs/helpers';
4
4
  import { computeIndexKeys, generateHTMLAndCSS } from '../src/index.mjs';
5
+ import { NevermoreImage } from '../src/image.mjs';
6
+ import { Canvas } from '@environment-safe/canvas';
5
7
  import { readFile, writeFile } from 'node:fs/promises';
6
8
  import { join } from 'node:path';
7
9
  const { cwd } = process;
10
+ const __dirname = import.meta.dirname;
8
11
 
9
12
 
10
13
  const formatHTML = (html, css, isRaw=false)=>{
@@ -13,7 +16,7 @@ const formatHTML = (html, css, isRaw=false)=>{
13
16
  };
14
17
 
15
18
  yargs(hideBin(process.argv))
16
- .command('pseudotext [input-file]', 'process the input', (yargs) => {
19
+ .command('pseudotext [input-file]', 'transform text to poison', (yargs) => {
17
20
  return yargs
18
21
  .positional('input-file', {
19
22
  describe: 'file input'
@@ -72,6 +75,43 @@ yargs(hideBin(process.argv))
72
75
  process.stdout.write(output);
73
76
  }
74
77
  }
78
+ }).command('pseudoimage [input-file]', 'transform XOR image encoding', (yargs) => {
79
+ return yargs
80
+ .positional('input-file', {
81
+ describe: 'file input'
82
+ })
83
+ }, async (argv) => {
84
+ let image = null;
85
+ if(!argv['input-file']){
86
+ //TODO: pipe support
87
+ throw new Error('pipe not yet supported');
88
+ }else{
89
+ const options = {
90
+ url: argv['input-file'],
91
+ maskDir: join(__dirname, '..', 'textures')
92
+ };
93
+ if(argv['key']){
94
+ options.key = argv['key']
95
+ }
96
+ image = new NevermoreImage(options);
97
+ await image.ready;
98
+ }
99
+ if(image && argv['image-output']){
100
+ let canvas = null;
101
+ if(argv['encode']){
102
+ canvas = await image.encode();
103
+ }
104
+ if(argv['decode']){
105
+ canvas = await image.decode();
106
+ }
107
+ Canvas.save(argv['image-output'], canvas);
108
+ }else{
109
+ throw new Error('piped output not yet supported');
110
+ }
111
+ }).option('key', {
112
+ alias: 'K',
113
+ type: 'string',
114
+ description: 'key to use for decoding the image'
75
115
  }).option('unified-output', {
76
116
  alias: 'U',
77
117
  type: 'string',
@@ -80,6 +120,10 @@ yargs(hideBin(process.argv))
80
120
  alias: 'C',
81
121
  type: 'string',
82
122
  description: 'File to generate css into'
123
+ }).option('image-output', {
124
+ alias: 'I',
125
+ type: 'string',
126
+ description: 'File to output image to'
83
127
  }).option('html-output', {
84
128
  alias: 'H',
85
129
  type: 'string',
@@ -88,6 +132,14 @@ yargs(hideBin(process.argv))
88
132
  alias: 'r',
89
133
  type: 'boolean',
90
134
  description: 'Do not wrap the ouput'
135
+ }).option('encode', {
136
+ alias: 'E',
137
+ type: 'boolean',
138
+ description: 'inline encoding'
139
+ }).option('decode', {
140
+ alias: 'D',
141
+ type: 'boolean',
142
+ description: 'inline decoding'
91
143
  }).option('render-mode', {
92
144
  alias: 'm',
93
145
  type: 'string',
package/demo.mjs ADDED
@@ -0,0 +1,29 @@
1
+ import { channelXOR, NevermoreImage } from './src/image.mjs';
2
+ import { Canvas, Image } from '@environment-safe/canvas';
3
+
4
+ (async ()=>{
5
+ const image = new NevermoreImage({
6
+ url:'./test.jpg',
7
+ maskDir: './textures'
8
+ });
9
+ await image.ready;
10
+ const canvas = image.encode();
11
+ await Canvas.save('./encoded.jpg', canvas);
12
+ const decoded = image.decode();
13
+ await Canvas.save('./decoded.jpg', decoded);
14
+ /*
15
+ const canvas = await Canvas.load('./test.jpg');
16
+ const texture = await Canvas.load('./textures/A.jpg');
17
+ await Canvas.save('./t.jpg', canvas);
18
+ await Canvas.save('./x.jpg', texture);
19
+ const pixels = channelXOR(canvas, texture, 'red', 1);
20
+ const context = canvas.getContext('2d');
21
+ console.log(pixels, 0, 0, canvas.width, canvas.height)
22
+ context.putImageData(pixels, 0, 0, 0, 0, canvas.width, canvas.height);
23
+ await Canvas.save('./out.jpg', canvas);
24
+ const resetPixels = channelXOR(canvas, texture, 'red', -1);
25
+ context.putImageData(resetPixels, 0, 0, 0, 0, canvas.width, canvas.height);
26
+ await Canvas.save('./reset.jpg', canvas);
27
+ //*/
28
+
29
+ })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-nevermore",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "keywords": [
5
5
  "adversarial",
6
6
  "content",
@@ -18,6 +18,12 @@
18
18
  ".": {
19
19
  "import": "./src/index.mjs"
20
20
  },
21
+ "./image": {
22
+ "import": "./src/image.mjs"
23
+ },
24
+ "./src/image.mjs": {
25
+ "import": "./src/image.mjs"
26
+ },
21
27
  "./package.json": {
22
28
  "import": "./package.json"
23
29
  },
@@ -25,17 +31,20 @@
25
31
  "import": "./src/index.mjs"
26
32
  }
27
33
  },
28
- "bin":{
34
+ "bin": {
29
35
  "nevermore": "./bin/nevermore"
30
36
  },
31
- "scripts":{
37
+ "scripts": {
32
38
  "single-file-demo": "./bin/nevermore pseudotext -U output.html nevermore.txt",
33
39
  "pipe-demo": "cat nevermore.txt | ./bin/nevermore pseudotext",
34
40
  "demo": "./bin/nevermore pseudotext -H out.html -C out.css nevermore.txt",
41
+ "image-encode": "./bin/nevermore pseudoimage ./test.jpg --image-output out.jpg --encode",
42
+ "image-decode": "./bin/nevermore pseudoimage ./out.jpg --image-output decoded.jpg --decode",
35
43
  "help": "./bin/nevermore --help"
36
44
  },
37
45
  "dependencies": {
38
- "@environment-safe/file": "^0.4.0",
46
+ "@environment-safe/canvas": "^4.2.3",
47
+ "@environment-safe/file": "^0.4.3",
39
48
  "object-hash": "^3.0.0",
40
49
  "parse-english": "^7.0.0",
41
50
  "self-dict": "^1.0.0",
package/src/image.mjs ADDED
@@ -0,0 +1,186 @@
1
+ import { Canvas, Image } from '@environment-safe/canvas';
2
+ import { File, Path } from '@environment-safe/file';
3
+
4
+ export const channelXOR = (imageCanvas, textureCanvas, chn='', direction= 1)=>{
5
+ const imageContext = imageCanvas.getContext('2d', { willReadFrequently: true });
6
+ const textureContext = textureCanvas.getContext('2d', { willReadFrequently: true });
7
+ const texturePixels = textureContext.getImageData(0,0, textureCanvas.width, textureCanvas.height);
8
+ const newPixels = imageContext.getImageData(0,0, imageCanvas.width, imageCanvas.height);
9
+ const sx = imageCanvas.width; //getx
10
+ const sy = imageCanvas.height; //gety
11
+ const tx = textureCanvas.width; //getx
12
+ const ty = textureCanvas.height; //gety
13
+ let x = null;
14
+ let y = null;
15
+ let pxl = null;
16
+ let tex_r, tex_g, tex_b, mask_offset, pixel_offset, value = null;
17
+ let channel = '';
18
+ switch(chn.toLowerCase()){
19
+ case 'red':
20
+ case 'r':
21
+ channel = 'r';
22
+ break;
23
+ case 'green':
24
+ case 'g':
25
+ channel = 'g';
26
+ break;
27
+ case 'blue':
28
+ case 'b':
29
+ channel = 'b';
30
+ break;
31
+ }
32
+ //kernel_size = filter.length; //coming soon
33
+ const intervalWidth = 256;
34
+ for(y = 0; y < sy; y++){
35
+ for(x = 0; x < sx; x++){
36
+ mask_offset = (((y%ty)*((sx%tx)*4)) + ((x%tx)*4));
37
+ pixel_offset = ((y*(sx*4)) + (x*4));
38
+ tex_r = texturePixels.data[mask_offset ];
39
+ tex_g = texturePixels.data[mask_offset + 1];
40
+ tex_b = texturePixels.data[mask_offset + 2];
41
+ if(!(tex_r === tex_g && tex_g === tex_b)){
42
+ value = Math.floor((tex_r + tex_g + tex_b) / 3);
43
+ }else{
44
+ value = tex_r;
45
+ }
46
+ if(channel === 'r'){
47
+ newPixels.data[pixel_offset ] = (
48
+ newPixels.data[pixel_offset ] + direction*value + intervalWidth
49
+ )%intervalWidth;
50
+ }
51
+ if(channel === 'g'){
52
+ newPixels.data[pixel_offset+1] = (
53
+ newPixels.data[pixel_offset+1] + direction*value + intervalWidth
54
+ )%intervalWidth;
55
+ }
56
+ if(channel === 'b'){
57
+ newPixels.data[pixel_offset + 2] = (
58
+ newPixels.data[pixel_offset+2] + direction*value + intervalWidth
59
+ )%intervalWidth;
60
+ }
61
+ }
62
+ }
63
+ return newPixels;
64
+ };
65
+
66
+ const makeKey = (len, alpha)=>{
67
+ const alphabet = alpha || 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
68
+ let key = '';
69
+ for(let lcv=0; lcv < len; lcv++){
70
+ key += alphabet[Math.floor(Math.random()*alphabet.length)];
71
+ }
72
+ return key;
73
+ }
74
+
75
+ export class NevermoreImage{
76
+ constructor(options={}){
77
+ if(!(
78
+ options.masks || options.maskDir
79
+ )) throw new Error('a set of masks are required');
80
+ this.ready = new Promise(async (resolve, reject)=>{
81
+ // load all masks
82
+ // TODO: load from cache
83
+ let masks = options.masks;
84
+ const textureWork = [];
85
+ const textureDictionary = {};
86
+ if(options.maskDir){
87
+ const types = ['jpg', 'jpeg', 'gif', 'png'];
88
+ const files = (await File.list(options.maskDir)).filter((name)=>{
89
+ const parts = name.split('.');
90
+ const ext = parts.pop().toLowerCase();
91
+ return types.indexOf(ext) !== -1;
92
+ // TODO: support mime through magic numbers
93
+ });
94
+ const fileLoads = [];
95
+ for(let lcv=0; lcv<files.length; lcv++){
96
+ const pth = new Path(files[lcv]);
97
+ if(!pth.parsed) throw new Error('path not parsed');
98
+ const parsed = pth.parsed.posix || pth.parsed.win32;
99
+ const canvasLoad = Canvas.load(
100
+ Path.join(options.maskDir, files[lcv])
101
+ );
102
+ textureWork.push(new Promise(async (resolve)=>{
103
+ resolve({
104
+ name: parsed.name,
105
+ canvas: await canvasLoad
106
+ });
107
+ }))
108
+ fileLoads.push(Canvas.load(Path.join(options.maskDir, files[lcv])));
109
+ }
110
+ masks = await Promise.all(fileLoads);
111
+ const texturesLoaded = await Promise.all(textureWork);
112
+ for(let lcv=0; lcv<texturesLoaded.length; lcv++){
113
+ textureDictionary[
114
+ texturesLoaded[lcv].name
115
+ ] = texturesLoaded[lcv].canvas;
116
+ };
117
+ this.textureDictionary = textureDictionary;
118
+ }
119
+ this.masks = masks;
120
+ this.canvas = options.image;
121
+ // masks loaded, now load the base image
122
+ if(options.url){
123
+ this.canvas = await Canvas.load(options.url);
124
+ }
125
+ if(!this.canvas){
126
+ return reject(new Error(
127
+ 'no base image provided to Image File'
128
+ ));
129
+ }
130
+ //make an id
131
+ this.key = options.key || `${makeKey(5)}-${makeKey(5)}-${makeKey(5)}`;
132
+ console.log('KEY:', this.key);
133
+ //we're ready to do work
134
+ resolve();
135
+ });
136
+ }
137
+
138
+ transform(options={}){
139
+ const { channelKeys, direction, key } = options;
140
+ let pixels = null;
141
+ let channelKey = null;
142
+ const canvas = this.canvas;
143
+ for(let lcv=0; lcv < channelKeys.r.length; lcv++){
144
+ const texture = this.textureDictionary[channelKeys.r[lcv]];
145
+ const pixels = channelXOR(canvas, texture, 'red', direction);
146
+ const context = canvas.getContext('2d');
147
+ context.putImageData(pixels, 0, 0, 0, 0, canvas.width, canvas.height);
148
+ }
149
+ for(let lcv=0; lcv < channelKeys.r.length; lcv++){
150
+ const texture = this.textureDictionary[channelKeys.r[lcv]];
151
+ const pixels = channelXOR(canvas, texture, 'green', direction);
152
+ const context = canvas.getContext('2d');
153
+ context.putImageData(pixels, 0, 0, 0, 0, canvas.width, canvas.height);
154
+ }
155
+ for(let lcv=0; lcv < channelKeys.r.length; lcv++){
156
+ const texture = this.textureDictionary[channelKeys.r[lcv]];
157
+ const pixels = channelXOR(canvas, texture, 'blue', direction);
158
+ const context = canvas.getContext('2d');
159
+ context.putImageData(pixels, 0, 0, 0, 0, canvas.width, canvas.height);
160
+ }
161
+ // should this mutate? current answer: yes
162
+ return canvas;
163
+ }
164
+
165
+ encode(options={}){
166
+ const key = options.key || this.key;
167
+ const parts = key.split('-')
168
+ const channelKeys = {
169
+ r: parts[0].split(''),
170
+ g: parts[1].split(''),
171
+ b: parts[2].split('')
172
+ }
173
+ return this.transform({ channelKeys, key, direction: 1 });
174
+ }
175
+
176
+ decode(options={}){
177
+ const key = options.key || this.key;
178
+ const parts = key.split('-');
179
+ const channelKeys = {
180
+ r: parts[0].split('').reverse(),
181
+ g: parts[1].split('').reverse(),
182
+ b: parts[2].split('').reverse()
183
+ }
184
+ return this.transform({ channelKeys, key, direction: -1 });
185
+ }
186
+ }
package/test.jpg ADDED
Binary file
package/textures/A.jpg ADDED
Binary file
package/textures/B.jpg ADDED
Binary file
package/textures/C.jpg ADDED
Binary file
package/textures/D.jpg ADDED
Binary file
package/textures/E.jpg ADDED
Binary file
package/textures/F.jpg ADDED
Binary file
package/textures/G.jpg ADDED
Binary file
package/textures/H.jpg ADDED
Binary file
package/textures/I.jpg ADDED
Binary file
package/textures/J.jpg ADDED
Binary file
package/textures/K.jpg ADDED
Binary file
package/textures/L.jpg ADDED
Binary file
package/textures/M.jpg ADDED
Binary file
package/textures/N.jpg ADDED
Binary file
package/textures/O.jpg ADDED
Binary file
package/textures/P.jpg ADDED
Binary file
package/textures/Q.jpg ADDED
Binary file
package/textures/R.jpg ADDED
Binary file
package/textures/S.jpg ADDED
Binary file
package/textures/T.jpg ADDED
Binary file
package/textures/U.jpg ADDED
Binary file
package/textures/V.jpg ADDED
Binary file
package/textures/W.jpg ADDED
Binary file
package/textures/X.jpg ADDED
Binary file
package/textures/Y.jpg ADDED
Binary file
package/textures/Z.jpg ADDED
Binary file