From b6b8b03ea6dac807c6fd11df3ceea4d5f664547c Mon Sep 17 00:00:00 2001 From: Nathan Harrenstein Date: Thu, 20 Jul 2017 12:46:34 -0700 Subject: [PATCH 01/22] Adds scaffolding. --- bitmap-autobots/.eslintignore | 6 + bitmap-autobots/.eslintrc | 23 +++ bitmap-autobots/.gitignore | 136 ++++++++++++++++++ bitmap-autobots/README.md | 0 .../assets}/palette-bitmap.bmp | Bin bitmap-autobots/index.js | 0 bitmap-autobots/lib/bitmap-transformer.js | 0 bitmap-autobots/lib/bitmap.js | 0 bitmap-autobots/package.json | 17 +++ bitmap-autobots/test/bitmap-test.js | 0 .../test/bitmap-transformer-test.js | 0 11 files changed, 182 insertions(+) create mode 100644 bitmap-autobots/.eslintignore create mode 100644 bitmap-autobots/.eslintrc create mode 100644 bitmap-autobots/.gitignore create mode 100644 bitmap-autobots/README.md rename {assets => bitmap-autobots/assets}/palette-bitmap.bmp (100%) create mode 100644 bitmap-autobots/index.js create mode 100644 bitmap-autobots/lib/bitmap-transformer.js create mode 100644 bitmap-autobots/lib/bitmap.js create mode 100644 bitmap-autobots/package.json create mode 100644 bitmap-autobots/test/bitmap-test.js create mode 100644 bitmap-autobots/test/bitmap-transformer-test.js diff --git a/bitmap-autobots/.eslintignore b/bitmap-autobots/.eslintignore new file mode 100644 index 0000000..c6abca4 --- /dev/null +++ b/bitmap-autobots/.eslintignore @@ -0,0 +1,6 @@ +**/node_modules/* +**/vendor/* +**/*.min.js +**/coverage/* +**/build/* +**/assets/* diff --git a/bitmap-autobots/.eslintrc b/bitmap-autobots/.eslintrc new file mode 100644 index 0000000..c8dfef7 --- /dev/null +++ b/bitmap-autobots/.eslintrc @@ -0,0 +1,23 @@ +{ + "rules": { + "no-console": "off", + "indent": [ "error", 2 ], + "quotes": [ "error", "single" ], + "semi": ["error", "always"], + "linebreak-style": [ "error", "unix" ] + }, + "env": { + "es6": true, + "node": true, + "mocha": true, + "jasmine": true + }, + "parserOptions": { + "ecmaFeatures": { + "modules": true, + "experimentalObjectRestSpread": true, + "impliedStrict": true + } + }, + "extends": "eslint:recommended" +} diff --git a/bitmap-autobots/.gitignore b/bitmap-autobots/.gitignore new file mode 100644 index 0000000..345130c --- /dev/null +++ b/bitmap-autobots/.gitignore @@ -0,0 +1,136 @@ +# Created by https://www.gitignore.io/api/osx,vim,node,macos,windows + +### macOS ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + + +### OSX ### + +# Icon must end with two \r + +# Thumbnails + +# Files that might appear in the root of a volume + +# Directories potentially created on remote AFP share + +### Vim ### +# swap +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-v][a-z] +[._]sw[a-p] +# session +Session.vim +# temporary +.netrwhist +*~ +# auto-generated tag files +tags + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.gitignore.io/api/osx,vim,node,macos,windows diff --git a/bitmap-autobots/README.md b/bitmap-autobots/README.md new file mode 100644 index 0000000..e69de29 diff --git a/assets/palette-bitmap.bmp b/bitmap-autobots/assets/palette-bitmap.bmp similarity index 100% rename from assets/palette-bitmap.bmp rename to bitmap-autobots/assets/palette-bitmap.bmp diff --git a/bitmap-autobots/index.js b/bitmap-autobots/index.js new file mode 100644 index 0000000..e69de29 diff --git a/bitmap-autobots/lib/bitmap-transformer.js b/bitmap-autobots/lib/bitmap-transformer.js new file mode 100644 index 0000000..e69de29 diff --git a/bitmap-autobots/lib/bitmap.js b/bitmap-autobots/lib/bitmap.js new file mode 100644 index 0000000..e69de29 diff --git a/bitmap-autobots/package.json b/bitmap-autobots/package.json new file mode 100644 index 0000000..3c8c04a --- /dev/null +++ b/bitmap-autobots/package.json @@ -0,0 +1,17 @@ +{ + "name": "bitmap-autobots", + "version": "1.0.0", + "description": "", + "main": "index.js", + "directories": { + "test": "test" + }, + "scripts": { + "start": "node index.js", + "test": "mocha", + "lint": "eslint" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/bitmap-autobots/test/bitmap-test.js b/bitmap-autobots/test/bitmap-test.js new file mode 100644 index 0000000..e69de29 diff --git a/bitmap-autobots/test/bitmap-transformer-test.js b/bitmap-autobots/test/bitmap-transformer-test.js new file mode 100644 index 0000000..e69de29 From 2856aebe0537b52eaa2f596e34470896b2d13edc Mon Sep 17 00:00:00 2001 From: Nathan Harrenstein Date: Thu, 20 Jul 2017 12:58:59 -0700 Subject: [PATCH 02/22] Change license and update lint script. --- bitmap-autobots/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bitmap-autobots/package.json b/bitmap-autobots/package.json index 3c8c04a..4276148 100644 --- a/bitmap-autobots/package.json +++ b/bitmap-autobots/package.json @@ -9,9 +9,9 @@ "scripts": { "start": "node index.js", "test": "mocha", - "lint": "eslint" + "lint": "eslint ." }, "keywords": [], "author": "", - "license": "ISC" + "license": "MIT" } From c7652038cf793e9d928a69d473e1daa28450da77 Mon Sep 17 00:00:00 2001 From: Bret Ldenburg Date: Thu, 20 Jul 2017 14:20:40 -0700 Subject: [PATCH 03/22] worked on the bitmap header --- bitmap-autobots/lib/bitmap.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/bitmap-autobots/lib/bitmap.js b/bitmap-autobots/lib/bitmap.js index e69de29..fa479fb 100644 --- a/bitmap-autobots/lib/bitmap.js +++ b/bitmap-autobots/lib/bitmap.js @@ -0,0 +1,24 @@ +'use strict'; + +const fs = require('fs'); + +function Bitmap(filepath) { + fs.readFile(`${__dirname}/..assets/palette-bitmap.bmp`, function(err, buffer) { + if (err) throw err; + console.log(buffer); + this.buffer = buffer; + readHeader(); + }); + + function readHeader() { + this.type = buffer.toString('utf-8', 0, 2); + this.size = buffer.readInt32LE(2); + this.reserved1 = buffer.readInt32LE(6); + this.reserved2 = buffer.readInt32LE(8); + this.fileOffset = buffer.readInt32LE(10); + }; + + function readDibHeader() { + + }; +}; From a35dd0d11735d6d2cfe2a265680f48d17369a7c7 Mon Sep 17 00:00:00 2001 From: Bret Ldenburg Date: Thu, 20 Jul 2017 15:18:33 -0700 Subject: [PATCH 04/22] got the file header to read to the console. --- bitmap-autobots/lib/bitmap.js | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/bitmap-autobots/lib/bitmap.js b/bitmap-autobots/lib/bitmap.js index fa479fb..3fd142d 100644 --- a/bitmap-autobots/lib/bitmap.js +++ b/bitmap-autobots/lib/bitmap.js @@ -1,24 +1,21 @@ 'use strict'; const fs = require('fs'); +module.exports = Bitmap; function Bitmap(filepath) { - fs.readFile(`${__dirname}/..assets/palette-bitmap.bmp`, function(err, buffer) { + fs.readFile(filepath, (err, buffer) => { if (err) throw err; - console.log(buffer); this.buffer = buffer; - readHeader(); + readHeader.call(this); + }); function readHeader() { - this.type = buffer.toString('utf-8', 0, 2); - this.size = buffer.readInt32LE(2); - this.reserved1 = buffer.readInt32LE(6); - this.reserved2 = buffer.readInt32LE(8); - this.fileOffset = buffer.readInt32LE(10); - }; - - function readDibHeader() { - - }; -}; + this.type = this.buffer.toString('utf-8', 0, 2); + this.size = this.buffer.readInt32LE(2); + this.reserved1 = this.buffer.readInt32LE(6); + this.reserved2 = this.buffer.readInt32LE(8); + this.pixelArrayOffset = this.buffer.readInt32LE(10); + } +} From a6d2e201b67c6f5270cffab0250fe8dcc982d723 Mon Sep 17 00:00:00 2001 From: Bret Ldenburg Date: Thu, 20 Jul 2017 16:20:15 -0700 Subject: [PATCH 05/22] wrote some logic to read the hex numbers of the color table --- bitmap-autobots/lib/bitmap.js | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/bitmap-autobots/lib/bitmap.js b/bitmap-autobots/lib/bitmap.js index 3fd142d..6ea5c8b 100644 --- a/bitmap-autobots/lib/bitmap.js +++ b/bitmap-autobots/lib/bitmap.js @@ -7,15 +7,26 @@ function Bitmap(filepath) { fs.readFile(filepath, (err, buffer) => { if (err) throw err; this.buffer = buffer; - readHeader.call(this); + readHeader.call(this, buffer); + readColorTable.call(this); }); - function readHeader() { - this.type = this.buffer.toString('utf-8', 0, 2); - this.size = this.buffer.readInt32LE(2); - this.reserved1 = this.buffer.readInt32LE(6); - this.reserved2 = this.buffer.readInt32LE(8); - this.pixelArrayOffset = this.buffer.readInt32LE(10); + function readHeader(buffer) { + this.type = buffer.toString('utf-8', 0, 2); + this.size = buffer.readInt32LE(2); + this.reserved1 = buffer.readInt32LE(6); + this.reserved2 = buffer.readInt32LE(8); + this.pixelArrayOffset = buffer.readInt32LE(10); + } + + function readColorTable() { + this.colors = []; + var colorTableOffset = 14 + this.dibHeaderSize; + var colorTableSize = this.colors * 4; + for(var i = colorTableOffset; i <= colorTableOffset + colorTableSize; i+=4) { + var colorHexString = this.buffer.toString('hex', i, i + 4); + this.colors.push(colorHexString); + } } } From 932c5f56c05d8f8af037956390d4b37985844cc2 Mon Sep 17 00:00:00 2001 From: Nathan Harrenstein Date: Thu, 20 Jul 2017 17:03:23 -0700 Subject: [PATCH 06/22] Reads pixel array. --- bitmap-autobots/lib/bitmap.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/bitmap-autobots/lib/bitmap.js b/bitmap-autobots/lib/bitmap.js index 6ea5c8b..6e44be3 100644 --- a/bitmap-autobots/lib/bitmap.js +++ b/bitmap-autobots/lib/bitmap.js @@ -9,7 +9,7 @@ function Bitmap(filepath) { this.buffer = buffer; readHeader.call(this, buffer); readColorTable.call(this); - + readPixelArray.call(this); }); function readHeader(buffer) { @@ -29,4 +29,24 @@ function Bitmap(filepath) { this.colors.push(colorHexString); } } + + function readPixelArray() { + let pixelRowSize = ((this.bitsPerPixel * this.width + 31) / 32) * 4; + let pixelArraySize = this.pixelRowSize * Math.abs(this.height); + + let pixelData = this.buffer.slice(this.pixelArrayOffset, pixelArraySize); + this.pixelArray = []; + + for (var i = this.height; i >= 0; i--) { + let pixelRow = []; + let pixelRowData = pixelData.slice(pixelRowSize * i, pixelRowSize); + + for (var j = 0; j < pixelRowSize; j += this.bitsPerPixel) { + var pixel = pixelRowData.toString('hex', j, j + this.bitsPerPixel); + pixelRow.push(pixel); + } + + this.pixelArray.push(pixelRow); + } + } } From 9f80a1309bf8a672f03edafc9ce1123287995e00 Mon Sep 17 00:00:00 2001 From: Nathan Harrenstein Date: Thu, 20 Jul 2017 22:42:08 -0700 Subject: [PATCH 07/22] Adds DIB header and fixes pixel array and color table. --- bitmap-autobots/lib/bitmap.js | 75 ++++++++++++++++++++++++++--------- 1 file changed, 56 insertions(+), 19 deletions(-) diff --git a/bitmap-autobots/lib/bitmap.js b/bitmap-autobots/lib/bitmap.js index 6e44be3..5157764 100644 --- a/bitmap-autobots/lib/bitmap.js +++ b/bitmap-autobots/lib/bitmap.js @@ -8,8 +8,9 @@ function Bitmap(filepath) { if (err) throw err; this.buffer = buffer; readHeader.call(this, buffer); - readColorTable.call(this); - readPixelArray.call(this); + readBitmapHeader.call(this, buffer); + readColorTable.call(this, buffer); + readPixelArray.call(this, buffer); }); function readHeader(buffer) { @@ -17,36 +18,72 @@ function Bitmap(filepath) { this.size = buffer.readInt32LE(2); this.reserved1 = buffer.readInt32LE(6); this.reserved2 = buffer.readInt32LE(8); - this.pixelArrayOffset = buffer.readInt32LE(10); + this.pixelArrayByteOffset = buffer.readInt32LE(10); } - function readColorTable() { + function readBitmapHeader(buffer) { + this.bitmapHeaderSize = buffer.readUInt32LE(14); + + switch (this.bitmapHeaderSize) { + case 40: + readBitmapInfoHeader.call(this, buffer); + break; + case 12: + case 16: + case 52: + case 56: + case 108: + case 124: + default: + throw new Error('The bitmap\'s header type is currently unsupported.'); + } + } + + function readBitmapInfoHeader(buffer) { + this.width = buffer.readInt32LE(18); + this.height = buffer.readInt32LE(22); + this.colorPlanes = buffer.readUInt16LE(26); + this.bitsPerPixel = buffer.readUInt16LE(28); + this.compression = buffer.readUInt32LE(30); + this.size = buffer.readUInt32LE(34); + this.horizontalResolution = buffer.readInt32LE(38); + this.verticalResolution = buffer.readInt32LE(42); + this.colorCount = buffer.readUInt32LE(46); + this.importantColorCount = buffer.readUInt32LE(50); + } + + function readColorTable(buffer) { this.colors = []; - var colorTableOffset = 14 + this.dibHeaderSize; - var colorTableSize = this.colors * 4; - for(var i = colorTableOffset; i <= colorTableOffset + colorTableSize; i+=4) { - var colorHexString = this.buffer.toString('hex', i, i + 4); + let colorTableOffset = 14 + this.bitmapHeaderSize; + let colorTableSize = this.colorCount * 4; + for(let i = colorTableOffset; i <= colorTableOffset + colorTableSize; i += 4) { + let s = buffer.toString('hex', i, i + 4); + let colorHexString = s[6] + s[7] + s[4] + s[5] + s[2] + s[3] + s[0] + s[1]; this.colors.push(colorHexString); } } - function readPixelArray() { - let pixelRowSize = ((this.bitsPerPixel * this.width + 31) / 32) * 4; - let pixelArraySize = this.pixelRowSize * Math.abs(this.height); + function readPixelArray(buffer) { + let rowSizeInBytes = Math.ceil(this.bitsPerPixel * this.width / 32) * 4; + let bytesPerPixel = this.bitsPerPixel / 8; - let pixelData = this.buffer.slice(this.pixelArrayOffset, pixelArraySize); this.pixelArray = []; - for (var i = this.height; i >= 0; i--) { + for (let row = this.height - 1; row >= 0; row--) { let pixelRow = []; - let pixelRowData = pixelData.slice(pixelRowSize * i, pixelRowSize); + let rowOffset = this.pixelArrayByteOffset + row * rowSizeInBytes; - for (var j = 0; j < pixelRowSize; j += this.bitsPerPixel) { - var pixel = pixelRowData.toString('hex', j, j + this.bitsPerPixel); - pixelRow.push(pixel); + for (let pixel = 0; pixel < this.width; pixel++) { + let pixelOffset = rowOffset + pixel * bytesPerPixel; + + if (this.bitsPerPixel < 16) { + pixelRow.push(this.colors[buffer.readUInt8(pixelOffset)]); + } else { + pixelRow.push(buffer.toString('hex', pixelOffset, bytesPerPixel)); + } } - + this.pixelArray.push(pixelRow); } } -} +} \ No newline at end of file From be755da0b63cf73b04c776d6e68431e086b97cb0 Mon Sep 17 00:00:00 2001 From: Nathan Harrenstein Date: Thu, 20 Jul 2017 23:16:18 -0700 Subject: [PATCH 08/22] Refactors module to export function which provides the constructed bitmap in a callback. --- bitmap-autobots/lib/bitmap.js | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/bitmap-autobots/lib/bitmap.js b/bitmap-autobots/lib/bitmap.js index 5157764..e1f2b64 100644 --- a/bitmap-autobots/lib/bitmap.js +++ b/bitmap-autobots/lib/bitmap.js @@ -1,19 +1,24 @@ 'use strict'; const fs = require('fs'); -module.exports = Bitmap; -function Bitmap(filepath) { - fs.readFile(filepath, (err, buffer) => { - if (err) throw err; - this.buffer = buffer; - readHeader.call(this, buffer); - readBitmapHeader.call(this, buffer); - readColorTable.call(this, buffer); - readPixelArray.call(this, buffer); +module.exports = function(filePath, callback) { + fs.readFile(filePath, (err, buffer) => { + if (err) throw callback(err); + + callback(null, new Bitmap(buffer)); }); +}; - function readHeader(buffer) { +function Bitmap(buffer) { + this.buffer = buffer; + + readHeader.call(this); + readBitmapHeader.call(this); + readColorTable.call(this); + readPixelArray.call(this); + + function readHeader() { this.type = buffer.toString('utf-8', 0, 2); this.size = buffer.readInt32LE(2); this.reserved1 = buffer.readInt32LE(6); @@ -21,7 +26,7 @@ function Bitmap(filepath) { this.pixelArrayByteOffset = buffer.readInt32LE(10); } - function readBitmapHeader(buffer) { + function readBitmapHeader() { this.bitmapHeaderSize = buffer.readUInt32LE(14); switch (this.bitmapHeaderSize) { @@ -39,7 +44,7 @@ function Bitmap(filepath) { } } - function readBitmapInfoHeader(buffer) { + function readBitmapInfoHeader() { this.width = buffer.readInt32LE(18); this.height = buffer.readInt32LE(22); this.colorPlanes = buffer.readUInt16LE(26); @@ -52,7 +57,7 @@ function Bitmap(filepath) { this.importantColorCount = buffer.readUInt32LE(50); } - function readColorTable(buffer) { + function readColorTable() { this.colors = []; let colorTableOffset = 14 + this.bitmapHeaderSize; let colorTableSize = this.colorCount * 4; @@ -63,7 +68,7 @@ function Bitmap(filepath) { } } - function readPixelArray(buffer) { + function readPixelArray() { let rowSizeInBytes = Math.ceil(this.bitsPerPixel * this.width / 32) * 4; let bytesPerPixel = this.bitsPerPixel / 8; From 4fc7628405de3374464f78b8b28faf416829941a Mon Sep 17 00:00:00 2001 From: Nathan Harrenstein Date: Fri, 21 Jul 2017 13:13:49 -0700 Subject: [PATCH 09/22] BGRa to RGBa --- bitmap-autobots/index.js | 7 +++++++ bitmap-autobots/lib/bitmap.js | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bitmap-autobots/index.js b/bitmap-autobots/index.js index e69de29..6d4d93e 100644 --- a/bitmap-autobots/index.js +++ b/bitmap-autobots/index.js @@ -0,0 +1,7 @@ +'use strict'; + +const createBitmap = require('./lib/bitmap.js'); + +createBitmap(`${__dirname}/assets/palette-bitmap.bmp`, function(err, bitmap) { + console.log(bitmap); +}); \ No newline at end of file diff --git a/bitmap-autobots/lib/bitmap.js b/bitmap-autobots/lib/bitmap.js index e1f2b64..0e18ebf 100644 --- a/bitmap-autobots/lib/bitmap.js +++ b/bitmap-autobots/lib/bitmap.js @@ -63,7 +63,7 @@ function Bitmap(buffer) { let colorTableSize = this.colorCount * 4; for(let i = colorTableOffset; i <= colorTableOffset + colorTableSize; i += 4) { let s = buffer.toString('hex', i, i + 4); - let colorHexString = s[6] + s[7] + s[4] + s[5] + s[2] + s[3] + s[0] + s[1]; + let colorHexString = s[4] + s[5] + s[2] + s[3] + s[0] + s[1] + s[6] + s[7]; this.colors.push(colorHexString); } } From fcc76f847bc5b799d49187decb6340a530804fc0 Mon Sep 17 00:00:00 2001 From: sharmarke Date: Fri, 21 Jul 2017 13:55:57 -0700 Subject: [PATCH 10/22] added get pixel function --- bitmap-autobots/lib/bitmap.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bitmap-autobots/lib/bitmap.js b/bitmap-autobots/lib/bitmap.js index 0e18ebf..b533a38 100644 --- a/bitmap-autobots/lib/bitmap.js +++ b/bitmap-autobots/lib/bitmap.js @@ -91,4 +91,7 @@ function Bitmap(buffer) { this.pixelArray.push(pixelRow); } } -} \ No newline at end of file + function getPixel(x, y) {; + return this.pixelArray[y][x]; + } +} From 4d04aae174c71e5244fe5f132b3360a15fe75d1c Mon Sep 17 00:00:00 2001 From: Nathan Harrenstein Date: Fri, 21 Jul 2017 14:38:12 -0700 Subject: [PATCH 11/22] Adds event emitter. --- bitmap-autobots/index.js | 10 +++++---- bitmap-autobots/lib/bitmap.js | 42 +++++++++++++++++++---------------- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/bitmap-autobots/index.js b/bitmap-autobots/index.js index 6d4d93e..a31a3a6 100644 --- a/bitmap-autobots/index.js +++ b/bitmap-autobots/index.js @@ -1,7 +1,9 @@ 'use strict'; -const createBitmap = require('./lib/bitmap.js'); +const Bitmap = require('./lib/bitmap.js'); -createBitmap(`${__dirname}/assets/palette-bitmap.bmp`, function(err, bitmap) { - console.log(bitmap); -}); \ No newline at end of file +let bitmap = new Bitmap(`${__dirname}/assets/palette-bitmap.bmp`); + +bitmap.on('loaded', function() { + console.log(bitmap.width); +}); diff --git a/bitmap-autobots/lib/bitmap.js b/bitmap-autobots/lib/bitmap.js index b533a38..830a4fa 100644 --- a/bitmap-autobots/lib/bitmap.js +++ b/bitmap-autobots/lib/bitmap.js @@ -1,24 +1,25 @@ 'use strict'; const fs = require('fs'); +const EventEmitter = require('events'); -module.exports = function(filePath, callback) { +module.exports = Bitmap; + +function Bitmap(filePath) { fs.readFile(filePath, (err, buffer) => { - if (err) throw callback(err); + if (err) throw err; - callback(null, new Bitmap(buffer)); - }); -}; + this.buffer = buffer; -function Bitmap(buffer) { - this.buffer = buffer; + readHeader.call(this, buffer); + readBitmapHeader.call(this, buffer); + readColorTable.call(this, buffer); + readPixelArray.call(this, buffer); - readHeader.call(this); - readBitmapHeader.call(this); - readColorTable.call(this); - readPixelArray.call(this); + this.emit('loaded'); + }); - function readHeader() { + function readHeader(buffer) { this.type = buffer.toString('utf-8', 0, 2); this.size = buffer.readInt32LE(2); this.reserved1 = buffer.readInt32LE(6); @@ -26,7 +27,7 @@ function Bitmap(buffer) { this.pixelArrayByteOffset = buffer.readInt32LE(10); } - function readBitmapHeader() { + function readBitmapHeader(buffer) { this.bitmapHeaderSize = buffer.readUInt32LE(14); switch (this.bitmapHeaderSize) { @@ -44,7 +45,7 @@ function Bitmap(buffer) { } } - function readBitmapInfoHeader() { + function readBitmapInfoHeader(buffer) { this.width = buffer.readInt32LE(18); this.height = buffer.readInt32LE(22); this.colorPlanes = buffer.readUInt16LE(26); @@ -57,7 +58,7 @@ function Bitmap(buffer) { this.importantColorCount = buffer.readUInt32LE(50); } - function readColorTable() { + function readColorTable(buffer) { this.colors = []; let colorTableOffset = 14 + this.bitmapHeaderSize; let colorTableSize = this.colorCount * 4; @@ -68,7 +69,7 @@ function Bitmap(buffer) { } } - function readPixelArray() { + function readPixelArray(buffer) { let rowSizeInBytes = Math.ceil(this.bitsPerPixel * this.width / 32) * 4; let bytesPerPixel = this.bitsPerPixel / 8; @@ -91,7 +92,10 @@ function Bitmap(buffer) { this.pixelArray.push(pixelRow); } } - function getPixel(x, y) {; - return this.pixelArray[y][x]; - } + + // this.getPixel = function(x, y) { + // return this.pixelArray[y][x]; + // }; } + +Bitmap.prototype = new EventEmitter(); From df8bf3818baa13577665109809e302901961471d Mon Sep 17 00:00:00 2001 From: Nathan Harrenstein Date: Fri, 21 Jul 2017 17:34:29 -0700 Subject: [PATCH 12/22] Removes redundant callbacks. --- bitmap-autobots/index.js | 10 ++++----- bitmap-autobots/lib/bitmap.js | 41 ++++++++++++++++------------------- 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/bitmap-autobots/index.js b/bitmap-autobots/index.js index a31a3a6..5bb1ccf 100644 --- a/bitmap-autobots/index.js +++ b/bitmap-autobots/index.js @@ -1,9 +1,9 @@ 'use strict'; +const fs = require('fs'); const Bitmap = require('./lib/bitmap.js'); -let bitmap = new Bitmap(`${__dirname}/assets/palette-bitmap.bmp`); - -bitmap.on('loaded', function() { - console.log(bitmap.width); -}); +fs.readFile(`${__dirname}/assets/palette-bitmap.bmp`, (err, buffer) => { + if (err) console.error(err); + console.log(new Bitmap(buffer)); +}); \ No newline at end of file diff --git a/bitmap-autobots/lib/bitmap.js b/bitmap-autobots/lib/bitmap.js index 830a4fa..a729350 100644 --- a/bitmap-autobots/lib/bitmap.js +++ b/bitmap-autobots/lib/bitmap.js @@ -1,25 +1,26 @@ 'use strict'; -const fs = require('fs'); const EventEmitter = require('events'); module.exports = Bitmap; -function Bitmap(filePath) { - fs.readFile(filePath, (err, buffer) => { - if (err) throw err; - - this.buffer = buffer; +function Bitmap(buffer) { + readHeader.call(this); + readBitmapHeader.call(this); + readColorTable.call(this); + readPixelArray.call(this); - readHeader.call(this, buffer); - readBitmapHeader.call(this, buffer); - readColorTable.call(this, buffer); - readPixelArray.call(this, buffer); + this.emit('loaded'); - this.emit('loaded'); - }); + this.copy = function() { + return new Bitmap(Buffer.from(buffer)); + }; - function readHeader(buffer) { + this.getPixel = function(x, y) { + return this.pixelArray[y][x]; + }; + + function readHeader() { this.type = buffer.toString('utf-8', 0, 2); this.size = buffer.readInt32LE(2); this.reserved1 = buffer.readInt32LE(6); @@ -27,7 +28,7 @@ function Bitmap(filePath) { this.pixelArrayByteOffset = buffer.readInt32LE(10); } - function readBitmapHeader(buffer) { + function readBitmapHeader() { this.bitmapHeaderSize = buffer.readUInt32LE(14); switch (this.bitmapHeaderSize) { @@ -45,7 +46,7 @@ function Bitmap(filePath) { } } - function readBitmapInfoHeader(buffer) { + function readBitmapInfoHeader() { this.width = buffer.readInt32LE(18); this.height = buffer.readInt32LE(22); this.colorPlanes = buffer.readUInt16LE(26); @@ -58,7 +59,7 @@ function Bitmap(filePath) { this.importantColorCount = buffer.readUInt32LE(50); } - function readColorTable(buffer) { + function readColorTable() { this.colors = []; let colorTableOffset = 14 + this.bitmapHeaderSize; let colorTableSize = this.colorCount * 4; @@ -69,7 +70,7 @@ function Bitmap(filePath) { } } - function readPixelArray(buffer) { + function readPixelArray() { let rowSizeInBytes = Math.ceil(this.bitsPerPixel * this.width / 32) * 4; let bytesPerPixel = this.bitsPerPixel / 8; @@ -92,10 +93,6 @@ function Bitmap(filePath) { this.pixelArray.push(pixelRow); } } - - // this.getPixel = function(x, y) { - // return this.pixelArray[y][x]; - // }; } -Bitmap.prototype = new EventEmitter(); +Bitmap.prototype = new EventEmitter(); \ No newline at end of file From f895cb65d7549568725658a5b7344a61bb9d311a Mon Sep 17 00:00:00 2001 From: Nathan Harrenstein Date: Fri, 21 Jul 2017 22:10:35 -0700 Subject: [PATCH 13/22] Adds bitmap transformer. --- bitmap-autobots/index.js | 22 ++++++- bitmap-autobots/lib/bitmap-transformer.js | 77 +++++++++++++++++++++++ bitmap-autobots/lib/bitmap.js | 42 +++++++------ bitmap-autobots/lib/color.js | 62 ++++++++++++++++++ 4 files changed, 182 insertions(+), 21 deletions(-) create mode 100644 bitmap-autobots/lib/color.js diff --git a/bitmap-autobots/index.js b/bitmap-autobots/index.js index 5bb1ccf..ac48979 100644 --- a/bitmap-autobots/index.js +++ b/bitmap-autobots/index.js @@ -2,8 +2,28 @@ const fs = require('fs'); const Bitmap = require('./lib/bitmap.js'); +require('./lib/bitmap-transformer.js'); fs.readFile(`${__dirname}/assets/palette-bitmap.bmp`, (err, buffer) => { if (err) console.error(err); - console.log(new Bitmap(buffer)); + + let bitmap = new Bitmap(buffer); + + let bw = bitmap.toBlackAndWhite(); + + fs.writeFile('./black-and-white.bmp', bw.buffer, 'binary', function(err) { + if (err) console.error(err); + }); + + let gs = bitmap.toGrayscale(); + + fs.writeFile('./greyscale.bmp', gs.buffer, 'binary', function(err) { + if (err) console.error(err); + }); + + let inv = bitmap.toInverse(); + + fs.writeFile('./inverse.bmp', inv.buffer, 'binary', function(err) { + if (err) console.error(err); + }); }); \ No newline at end of file diff --git a/bitmap-autobots/lib/bitmap-transformer.js b/bitmap-autobots/lib/bitmap-transformer.js index e69de29..41e1181 100644 --- a/bitmap-autobots/lib/bitmap-transformer.js +++ b/bitmap-autobots/lib/bitmap-transformer.js @@ -0,0 +1,77 @@ +'use strict'; + +const Bitmap = require('./bitmap.js'); + +module.exports = BitmapTransformer; + +function BitmapTransformer(bitmap) { + this.bitmap = bitmap; + + this.setPixel = function(x, y, color) { + let pixelOffset = bitmap.pixelArrayByteOffset + y * bitmap.rowSizeInBytes + x * bitmap.bytesPerPixel; + let colorIndex = bitmap.colors.indexOf(color); + + if (colorIndex === -1) { + colorIndex = this.addColor(color); + } + + bitmap.buffer.writeUInt8(colorIndex, pixelOffset); + }; + + this.addColor = function(color) { + let uniqueColors = {}; + + for (let i = 0; i < bitmap.colors.length; i++) { + let uniqueColor = uniqueColors[bitmap.colors[i].toRGBAString()]; + + if (uniqueColor) { + bitmap.colors[i] = color; + return i; + } + else { + uniqueColors[color.toRGBAString()] = bitmap.colors[i]; + } + } + }; + + this.writeColors = function() { + let bgraColors = bitmap.colors.map(c => c.toBGRAString()).join(''); + bitmap.buffer.write(bgraColors, bitmap.colorTableOffset, bitmap.colorTableSize, 'hex'); + }; +} + +Bitmap.prototype.toGrayscale = function() { + let clone = this.clone(); + let bitmapTransformer = new BitmapTransformer(clone); + + for (let i = 0; i < clone.colors.length; i++) { + clone.colors[i] = clone.colors[i].toGrayscale(); + } + + bitmapTransformer.writeColors(); + return clone; +}; + +Bitmap.prototype.toInverse = function() { + let clone = this.clone(); + let bitmapTransformer = new BitmapTransformer(clone); + + for (let i = 0; i < clone.colors.length; i++) { + clone.colors[i] = clone.colors[i].toInverse(); + } + + bitmapTransformer.writeColors(); + return clone; +}; + +Bitmap.prototype.toBlackAndWhite = function() { + let clone = this.clone(); + let bitmapTransformer = new BitmapTransformer(clone); + + for (let i = 0; i < clone.colors.length; i++) { + clone.colors[i] = clone.colors[i].toBlackAndWhite(); + } + + bitmapTransformer.writeColors(); + return clone; +}; \ No newline at end of file diff --git a/bitmap-autobots/lib/bitmap.js b/bitmap-autobots/lib/bitmap.js index a729350..0ad27b0 100644 --- a/bitmap-autobots/lib/bitmap.js +++ b/bitmap-autobots/lib/bitmap.js @@ -1,19 +1,19 @@ 'use strict'; -const EventEmitter = require('events'); +const Color = require('./color.js'); module.exports = Bitmap; function Bitmap(buffer) { + this.buffer = buffer; + readHeader.call(this); readBitmapHeader.call(this); readColorTable.call(this); readPixelArray.call(this); - this.emit('loaded'); - - this.copy = function() { - return new Bitmap(Buffer.from(buffer)); + this.clone = function() { + return new Bitmap(Buffer.from(this.buffer)); }; this.getPixel = function(x, y) { @@ -61,38 +61,40 @@ function Bitmap(buffer) { function readColorTable() { this.colors = []; - let colorTableOffset = 14 + this.bitmapHeaderSize; - let colorTableSize = this.colorCount * 4; - for(let i = colorTableOffset; i <= colorTableOffset + colorTableSize; i += 4) { - let s = buffer.toString('hex', i, i + 4); - let colorHexString = s[4] + s[5] + s[2] + s[3] + s[0] + s[1] + s[6] + s[7]; - this.colors.push(colorHexString); + this.colorTableOffset = 14 + this.bitmapHeaderSize; + this.colorTableSize = this.colorCount * 4; + + for (let i = this.colorTableOffset; i <= this.colorTableOffset + this.colorTableSize; i += 4) { + let bgraHex = buffer.toString('hex', i, i + 4); + let color = Color.fromBGRAHex(bgraHex); + this.colors.push(color); } } function readPixelArray() { - let rowSizeInBytes = Math.ceil(this.bitsPerPixel * this.width / 32) * 4; - let bytesPerPixel = this.bitsPerPixel / 8; + this.rowSizeInBytes = Math.ceil(this.bitsPerPixel * this.width / 32) * 4; + this.bytesPerPixel = this.bitsPerPixel / 8; this.pixelArray = []; for (let row = this.height - 1; row >= 0; row--) { let pixelRow = []; - let rowOffset = this.pixelArrayByteOffset + row * rowSizeInBytes; + let rowOffset = this.pixelArrayByteOffset + row * this.rowSizeInBytes; for (let pixel = 0; pixel < this.width; pixel++) { - let pixelOffset = rowOffset + pixel * bytesPerPixel; + let pixelOffset = rowOffset + pixel * this.bytesPerPixel; if (this.bitsPerPixel < 16) { - pixelRow.push(this.colors[buffer.readUInt8(pixelOffset)]); + let colorIndex = buffer.readUInt8(pixelOffset); + pixelRow.push(this.colors[colorIndex]); } else { - pixelRow.push(buffer.toString('hex', pixelOffset, bytesPerPixel)); + let bgraHex = buffer.toString('hex', pixelOffset, this.bytesPerPixel); + let color = Color.fromBGRAHex(bgraHex); + pixelRow.push(color); } } this.pixelArray.push(pixelRow); } } -} - -Bitmap.prototype = new EventEmitter(); \ No newline at end of file +} \ No newline at end of file diff --git a/bitmap-autobots/lib/color.js b/bitmap-autobots/lib/color.js new file mode 100644 index 0000000..624753a --- /dev/null +++ b/bitmap-autobots/lib/color.js @@ -0,0 +1,62 @@ +'use strict'; + +module.exports = Color; + +function Color(red, green, blue, alpha) { + this.red = red; + this.green = green; + this.blue = blue; + this.alpha = alpha; + + this.toBGRAString = function() { + return toPaddedHex(blue) + toPaddedHex(green) + toPaddedHex(red) + toPaddedHex(alpha); + }; + + this.toRGBAString = function() { + return toPaddedHex(red) + toPaddedHex(green) + toPaddedHex(blue) + toPaddedHex(alpha); + }; + + this.toGrayscale = function() { + let average = Math.round((this.red + this.green + this.blue) / 3); + return new Color(average, average, average, this.alpha); + }; + + this.toInverse = function() { + return new Color(255 - this.red, 255 - this.green, 255 - this.blue, this.alpha); + }; + + this.toBlackAndWhite = function() { + let average = Math.round((this.red + this.green + this.blue) / 3); + let result = average < 128 ? 0 : 255; + + return new Color(result, result, result, this.alpha); + }; + + function toPaddedHex(channel) { + channel = channel.toString(16); + + if (channel.length === 1) { + return '0' + channel; + } + + return channel; + } +} + +Color.fromRGBAHex = function(rgbaHex) { + let red = parseInt(rgbaHex.slice(0, 2), 16); + let green = parseInt(rgbaHex.slice(2, 4), 16); + let blue = parseInt(rgbaHex.slice(4, 6), 16); + let alpha = parseInt(rgbaHex.slice(6, 8), 16); + + return new Color(red, green, blue, alpha); +}; + +Color.fromBGRAHex = function(rgbaHex) { + let blue = parseInt(rgbaHex.slice(0, 2), 16); + let green = parseInt(rgbaHex.slice(2, 4), 16); + let red = parseInt(rgbaHex.slice(4, 6), 16); + let alpha = parseInt(rgbaHex.slice(6, 8), 16); + + return new Color(red, green, blue, alpha); +}; \ No newline at end of file From bc0516c1525d19cfc556bee53370b35e55cc2397 Mon Sep 17 00:00:00 2001 From: Nathan Harrenstein Date: Sat, 22 Jul 2017 14:50:07 -0700 Subject: [PATCH 14/22] Adds more transforms and color conversions to and from hsl. --- bitmap-autobots/.gitignore | 2 + bitmap-autobots/README.md | 3 + bitmap-autobots/index.js | 66 +++++++--- bitmap-autobots/lib/bitmap-transformer.js | 139 +++++++++++++++++++--- bitmap-autobots/lib/color.js | 81 ++++++++++++- 5 files changed, 255 insertions(+), 36 deletions(-) diff --git a/bitmap-autobots/.gitignore b/bitmap-autobots/.gitignore index 345130c..b232c07 100644 --- a/bitmap-autobots/.gitignore +++ b/bitmap-autobots/.gitignore @@ -134,3 +134,5 @@ $RECYCLE.BIN/ *.lnk # End of https://www.gitignore.io/api/osx,vim,node,macos,windows + +output diff --git a/bitmap-autobots/README.md b/bitmap-autobots/README.md index e69de29..bc041e1 100644 --- a/bitmap-autobots/README.md +++ b/bitmap-autobots/README.md @@ -0,0 +1,3 @@ +# Bitmap Transformer + +This project contains three modules which facilitate the transformation of bitmap files. The color module provides support for color manipulation as well as conversion to and from hex strings, the bitmap module parses a bitmap into properties and objects, and the bitmap transformer module provides methods for modifying a bitmap and attaches an assortment of transforms onto the bitmap's prototype. \ No newline at end of file diff --git a/bitmap-autobots/index.js b/bitmap-autobots/index.js index ac48979..59fc50f 100644 --- a/bitmap-autobots/index.js +++ b/bitmap-autobots/index.js @@ -4,26 +4,58 @@ const fs = require('fs'); const Bitmap = require('./lib/bitmap.js'); require('./lib/bitmap-transformer.js'); -fs.readFile(`${__dirname}/assets/palette-bitmap.bmp`, (err, buffer) => { - if (err) console.error(err); - - let bitmap = new Bitmap(buffer); - - let bw = bitmap.toBlackAndWhite(); +let checkOutputDirectoryExists = function() { + return new Promise(function(resolve, reject) { + fs.exists('./output/', function(exists) { + if (exists) { + resolve(); + } + else { + reject(); + } + }); + }); +}; - fs.writeFile('./black-and-white.bmp', bw.buffer, 'binary', function(err) { - if (err) console.error(err); +let createOutputDirectory = function() { + return new Promise(function(resolve, reject) { + fs.mkdir('./output', function(err) { + if (err) reject(err); + resolve(); + }); }); +}; - let gs = bitmap.toGrayscale(); - - fs.writeFile('./greyscale.bmp', gs.buffer, 'binary', function(err) { - if (err) console.error(err); +let loadBitmap = function() { + return new Promise(function(resolve, reject) { + fs.readFile(`${__dirname}/assets/palette-bitmap.bmp`, function(err, buffer) { + if (err) reject(err); + resolve(new Bitmap(buffer)); + }); }); +}; - let inv = bitmap.toInverse(); - - fs.writeFile('./inverse.bmp', inv.buffer, 'binary', function(err) { - if (err) console.error(err); +let createOutput = function(fileName, bitmap, func) { + return new Promise(function(resolve, reject) { + let transformedBitmap = func(); + + fs.writeFile(fileName, transformedBitmap.buffer, 'binary', function(err) { + if (err) reject(err); + resolve(bitmap); + }); }); -}); \ No newline at end of file +}; + +checkOutputDirectoryExists() + .catch(createOutputDirectory) + .then(loadBitmap, console.error) + .then(bitmap => createOutput('./output/black-and-white.bmp', bitmap, () => bitmap.toBlackAndWhite()), console.error) + .then(bitmap => createOutput('./output/grayscale.bmp', bitmap, () => bitmap.toGrayscale()), console.error) + .then(bitmap => createOutput('./output/inverse-colors.bmp', bitmap, () => bitmap.toInverse()), console.error) + .then(bitmap => createOutput('./output/flipped-horizontally.bmp', bitmap, () => bitmap.flipHorizontally()), console.error) + .then(bitmap => createOutput('./output/flipped-vertically.bmp', bitmap, () => bitmap.flipVertically()), console.error) + .then(bitmap => createOutput('./output/rotate-clockwise.bmp', bitmap, () => bitmap.rotateClockwise()), console.error) + .then(bitmap => createOutput('./output/rotate-counterclockwise.bmp', bitmap, () => bitmap.rotateCounterclockwise()), console.error) + .then(bitmap => createOutput('./output/shift-hue.bmp', bitmap, () => bitmap.shiftHue(60)), console.error) + .then(bitmap => createOutput('./output/shift-saturation.bmp', bitmap, () => bitmap.shiftSaturation(-30)), console.error) + .then(bitmap => createOutput('./output/shift-lightness.bmp', bitmap, () => bitmap.shiftLightness(20)), console.error); \ No newline at end of file diff --git a/bitmap-autobots/lib/bitmap-transformer.js b/bitmap-autobots/lib/bitmap-transformer.js index 41e1181..a7e2414 100644 --- a/bitmap-autobots/lib/bitmap-transformer.js +++ b/bitmap-autobots/lib/bitmap-transformer.js @@ -1,22 +1,13 @@ 'use strict'; const Bitmap = require('./bitmap.js'); +const Color = require('./color.js'); module.exports = BitmapTransformer; function BitmapTransformer(bitmap) { this.bitmap = bitmap; - this.setPixel = function(x, y, color) { - let pixelOffset = bitmap.pixelArrayByteOffset + y * bitmap.rowSizeInBytes + x * bitmap.bytesPerPixel; - let colorIndex = bitmap.colors.indexOf(color); - - if (colorIndex === -1) { - colorIndex = this.addColor(color); - } - - bitmap.buffer.writeUInt8(colorIndex, pixelOffset); - }; this.addColor = function(color) { let uniqueColors = {}; @@ -38,15 +29,32 @@ function BitmapTransformer(bitmap) { let bgraColors = bitmap.colors.map(c => c.toBGRAString()).join(''); bitmap.buffer.write(bgraColors, bitmap.colorTableOffset, bitmap.colorTableSize, 'hex'); }; + + this.writePixels = function() { + for (var y = 0; y < bitmap.height; y++) { + for (var x = 0; x < bitmap.width; x++) { + this.writePixel(x, y, bitmap.pixelArray[y][x]); + } + } + }; + + this.writePixel = function(x, y, color) { + let pixelOffset = bitmap.pixelArrayByteOffset + (bitmap.height - 1 - y) * bitmap.rowSizeInBytes + x * bitmap.bytesPerPixel; + let colorIndex = bitmap.colors.indexOf(color); + + if (colorIndex === -1) { + colorIndex = this.addColor(color); + } + + bitmap.buffer.writeUInt8(colorIndex, pixelOffset); + }; } Bitmap.prototype.toGrayscale = function() { let clone = this.clone(); let bitmapTransformer = new BitmapTransformer(clone); - for (let i = 0; i < clone.colors.length; i++) { - clone.colors[i] = clone.colors[i].toGrayscale(); - } + clone.colors = clone.colors.map(c => c.toGrayscale()); bitmapTransformer.writeColors(); return clone; @@ -56,9 +64,7 @@ Bitmap.prototype.toInverse = function() { let clone = this.clone(); let bitmapTransformer = new BitmapTransformer(clone); - for (let i = 0; i < clone.colors.length; i++) { - clone.colors[i] = clone.colors[i].toInverse(); - } + clone.colors = clone.colors.map(c => c.toInverse()); bitmapTransformer.writeColors(); return clone; @@ -68,10 +74,107 @@ Bitmap.prototype.toBlackAndWhite = function() { let clone = this.clone(); let bitmapTransformer = new BitmapTransformer(clone); - for (let i = 0; i < clone.colors.length; i++) { - clone.colors[i] = clone.colors[i].toBlackAndWhite(); + clone.colors = clone.colors.map(c => c.toBlackAndWhite()); + + bitmapTransformer.writeColors(); + return clone; +}; + +Bitmap.prototype.flipHorizontally = function() { + let clone = this.clone(); + let bitmapTransformer = new BitmapTransformer(clone); + + for (var i = 0; i < clone.height; i++) { + clone.pixelArray[i] = clone.pixelArray[i].reverse(); } + bitmapTransformer.writePixels(); + return clone; +}; + +Bitmap.prototype.flipVertically = function() { + let clone = this.clone(); + let bitmapTransformer = new BitmapTransformer(clone); + + clone.pixelArray = clone.pixelArray.reverse(); + + bitmapTransformer.writePixels(); + return clone; +}; + +Bitmap.prototype.rotateClockwise = function() { + let clone = this.clone(); + let bitmapTransformer = new BitmapTransformer(clone); + + for (var y = 0; y < clone.height; y++) { + for (var x = 0; x < clone.width; x++) { + bitmapTransformer.writePixel(this.height - 1 - y, x, this.pixelArray[y][x]); + } + } + + return clone; +}; + +Bitmap.prototype.rotateCounterclockwise = function() { + let clone = this.clone(); + let bitmapTransformer = new BitmapTransformer(clone); + + for (var y = 0; y < clone.height; y++) { + for (var x = 0; x < clone.width; x++) { + bitmapTransformer.writePixel(y, this.width - 1 - x, this.pixelArray[y][x]); + } + } + + return clone; +}; + +Bitmap.prototype.shiftHue = function(degrees) { + let clone = this.clone(); + let bitmapTransformer = new BitmapTransformer(clone); + + clone.colors = clone.colors.map(c => { + c.getHSL(); + let hue = c.hue + degrees; + + if (Math.abs(hue) > 360) { + hue *= (1 / (hue / 360)); + } + + if (hue < 0) { + hue += 360; + } + + return Color.fromHSLA(hue, c.saturation, c.lightness, c.alpha); + }); + + bitmapTransformer.writeColors(); + return clone; +}; + +Bitmap.prototype.shiftSaturation = function(percentage) { + let clone = this.clone(); + let bitmapTransformer = new BitmapTransformer(clone); + + clone.colors = clone.colors.map(c => { + c.getHSL(); + let saturation = Math.min(1, Math.max(0, c.saturation + percentage / 100)); + return Color.fromHSLA(c.hue, saturation, c.lightness, c.alpha);; + }); + + bitmapTransformer.writeColors(); + return clone; +}; + +Bitmap.prototype.shiftLightness = function(percentage) { + let clone = this.clone(); + let bitmapTransformer = new BitmapTransformer(clone); + + clone.colors = clone.colors.map(c => { + c.getHSL(); + let lightness = Math.min(1, Math.max(0, c.lightness + percentage / 100)); + return Color.fromHSLA(c.hue, c.saturation, lightness, c.alpha); + }); + bitmapTransformer.writeColors(); return clone; }; \ No newline at end of file diff --git a/bitmap-autobots/lib/color.js b/bitmap-autobots/lib/color.js index 624753a..7f7623b 100644 --- a/bitmap-autobots/lib/color.js +++ b/bitmap-autobots/lib/color.js @@ -2,12 +2,48 @@ module.exports = Color; -function Color(red, green, blue, alpha) { +function Color(red, green, blue, alpha = 0) { this.red = red; this.green = green; this.blue = blue; this.alpha = alpha; + this.getHSL = function() { + let redness = this.red / 255; + let greenness = this.green / 255; + let blueness = this.blue / 255; + + let min = Math.min(redness, greenness, blueness); + let max = Math.max(redness, greenness, blueness); + let difference = max - min; + + this.hue = difference; + this.saturation = difference; + this.lightness = (min + max) / 2; + + if (difference === 0) { + this.saturation = 0; + this.hue = 0; + } else { + this.saturation = difference / (1 - Math.abs(2 * this.lightness - 1)); + switch (max) { + case redness: + this.hue = Math.round(60 * (((greenness - blueness) / difference) % 6)); + break; + case greenness: + this.hue = Math.round(60 * (((blueness - redness) / difference) + 2)); + break; + case blueness: + this.hue = Math.round(60 * (((redness - greenness) / difference) + 4)); + break; + } + + if (this.hue < 0) { + this.hue += 360; + } + } + }; + this.toBGRAString = function() { return toPaddedHex(blue) + toPaddedHex(green) + toPaddedHex(red) + toPaddedHex(alpha); }; @@ -59,4 +95,47 @@ Color.fromBGRAHex = function(rgbaHex) { let alpha = parseInt(rgbaHex.slice(6, 8), 16); return new Color(red, green, blue, alpha); +}; + +Color.fromHSLA = function(hue, saturation, lightness, alpha = 0) { + let chroma = (1 - Math.abs(2 * lightness - 1)) * saturation; + let h = hue / 60; + let x = chroma * (1 - Math.abs(h % 2 - 1)); + + let red = 0; + let green = 0; + let blue = 0; + + if (0 <= h && h <= 1) { + red = chroma; + green = x; + } else if (1 <= h && h <= 2) { + red = x; + green = chroma; + } else if (2 <= h && h <= 3) { + green = chroma; + blue = x; + } else if (3 <= h && h <= 4) { + green = x; + blue = chroma; + } else if (4 <= h && h <= 5) { + red = x; + blue = chroma; + } else if (5 <= h && h <= 6) { + red = chroma; + blue = x; + } + + let m = lightness - 0.5 * chroma; + + red = Math.round((red + m) * 255); + green = Math.round((green + m) * 255); + blue = Math.round((blue + m) * 255); + + let color = new Color(red, green, blue, alpha); + color.hue = hue; + color.saturation = saturation; + color.lightness = lightness; + + return color; }; \ No newline at end of file From 644bcebbee6d86b94d98c918f200d836a72ce2c8 Mon Sep 17 00:00:00 2001 From: Nathan Harrenstein Date: Sat, 22 Jul 2017 21:08:07 -0700 Subject: [PATCH 15/22] Adds bitmap tests. --- bitmap-autobots/index.js | 22 ++-- bitmap-autobots/lib/bitmap-transformer.js | 4 +- bitmap-autobots/lib/bitmap.js | 148 +++++++++++----------- bitmap-autobots/lib/color.js | 14 +- bitmap-autobots/package.json | 6 +- bitmap-autobots/test/bitmap-test.js | 71 +++++++++++ 6 files changed, 171 insertions(+), 94 deletions(-) diff --git a/bitmap-autobots/index.js b/bitmap-autobots/index.js index 59fc50f..c0a0562 100644 --- a/bitmap-autobots/index.js +++ b/bitmap-autobots/index.js @@ -39,7 +39,7 @@ let createOutput = function(fileName, bitmap, func) { return new Promise(function(resolve, reject) { let transformedBitmap = func(); - fs.writeFile(fileName, transformedBitmap.buffer, 'binary', function(err) { + fs.writeFile(`./output/${fileName}.bmp`, transformedBitmap.buffer, 'binary', function(err) { if (err) reject(err); resolve(bitmap); }); @@ -49,13 +49,13 @@ let createOutput = function(fileName, bitmap, func) { checkOutputDirectoryExists() .catch(createOutputDirectory) .then(loadBitmap, console.error) - .then(bitmap => createOutput('./output/black-and-white.bmp', bitmap, () => bitmap.toBlackAndWhite()), console.error) - .then(bitmap => createOutput('./output/grayscale.bmp', bitmap, () => bitmap.toGrayscale()), console.error) - .then(bitmap => createOutput('./output/inverse-colors.bmp', bitmap, () => bitmap.toInverse()), console.error) - .then(bitmap => createOutput('./output/flipped-horizontally.bmp', bitmap, () => bitmap.flipHorizontally()), console.error) - .then(bitmap => createOutput('./output/flipped-vertically.bmp', bitmap, () => bitmap.flipVertically()), console.error) - .then(bitmap => createOutput('./output/rotate-clockwise.bmp', bitmap, () => bitmap.rotateClockwise()), console.error) - .then(bitmap => createOutput('./output/rotate-counterclockwise.bmp', bitmap, () => bitmap.rotateCounterclockwise()), console.error) - .then(bitmap => createOutput('./output/shift-hue.bmp', bitmap, () => bitmap.shiftHue(60)), console.error) - .then(bitmap => createOutput('./output/shift-saturation.bmp', bitmap, () => bitmap.shiftSaturation(-30)), console.error) - .then(bitmap => createOutput('./output/shift-lightness.bmp', bitmap, () => bitmap.shiftLightness(20)), console.error); \ No newline at end of file + .then(bitmap => createOutput('black-and-white', bitmap, () => bitmap.toBlackAndWhite()), console.error) + .then(bitmap => createOutput('grayscale', bitmap, () => bitmap.toGrayscale()), console.error) + .then(bitmap => createOutput('inverse-colors', bitmap, () => bitmap.toInverse()), console.error) + .then(bitmap => createOutput('flipped-horizontally', bitmap, () => bitmap.flipHorizontally()), console.error) + .then(bitmap => createOutput('flipped-vertically', bitmap, () => bitmap.flipVertically()), console.error) + .then(bitmap => createOutput('rotate-clockwise', bitmap, () => bitmap.rotateClockwise()), console.error) + .then(bitmap => createOutput('rotate-counterclockwise', bitmap, () => bitmap.rotateCounterclockwise()), console.error) + .then(bitmap => createOutput('shift-hue', bitmap, () => bitmap.shiftHue(60)), console.error) + .then(bitmap => createOutput('shift-saturation', bitmap, () => bitmap.shiftSaturation(-30)), console.error) + .then(bitmap => createOutput('shift-lightness', bitmap, () => bitmap.shiftLightness(20)), console.error); \ No newline at end of file diff --git a/bitmap-autobots/lib/bitmap-transformer.js b/bitmap-autobots/lib/bitmap-transformer.js index a7e2414..0e5c036 100644 --- a/bitmap-autobots/lib/bitmap-transformer.js +++ b/bitmap-autobots/lib/bitmap-transformer.js @@ -39,7 +39,7 @@ function BitmapTransformer(bitmap) { }; this.writePixel = function(x, y, color) { - let pixelOffset = bitmap.pixelArrayByteOffset + (bitmap.height - 1 - y) * bitmap.rowSizeInBytes + x * bitmap.bytesPerPixel; + let pixelOffset = bitmap.pixelArrayOffset + (bitmap.height - 1 - y) * bitmap.rowSizeInBytes + x * bitmap.bytesPerPixel; let colorIndex = bitmap.colors.indexOf(color); if (colorIndex === -1) { @@ -158,7 +158,7 @@ Bitmap.prototype.shiftSaturation = function(percentage) { clone.colors = clone.colors.map(c => { c.getHSL(); let saturation = Math.min(1, Math.max(0, c.saturation + percentage / 100)); - return Color.fromHSLA(c.hue, saturation, c.lightness, c.alpha);; + return Color.fromHSLA(c.hue, saturation, c.lightness, c.alpha); }); bitmapTransformer.writeColors(); diff --git a/bitmap-autobots/lib/bitmap.js b/bitmap-autobots/lib/bitmap.js index 0ad27b0..af1fefb 100644 --- a/bitmap-autobots/lib/bitmap.js +++ b/bitmap-autobots/lib/bitmap.js @@ -5,6 +5,8 @@ const Color = require('./color.js'); module.exports = Bitmap; function Bitmap(buffer) { + if (!buffer) throw new ReferenceError('buffer cannot be undefined.'); + this.buffer = buffer; readHeader.call(this); @@ -19,82 +21,82 @@ function Bitmap(buffer) { this.getPixel = function(x, y) { return this.pixelArray[y][x]; }; - - function readHeader() { - this.type = buffer.toString('utf-8', 0, 2); - this.size = buffer.readInt32LE(2); - this.reserved1 = buffer.readInt32LE(6); - this.reserved2 = buffer.readInt32LE(8); - this.pixelArrayByteOffset = buffer.readInt32LE(10); - } - - function readBitmapHeader() { - this.bitmapHeaderSize = buffer.readUInt32LE(14); - - switch (this.bitmapHeaderSize) { - case 40: - readBitmapInfoHeader.call(this, buffer); - break; - case 12: - case 16: - case 52: - case 56: - case 108: - case 124: - default: - throw new Error('The bitmap\'s header type is currently unsupported.'); - } - } - - function readBitmapInfoHeader() { - this.width = buffer.readInt32LE(18); - this.height = buffer.readInt32LE(22); - this.colorPlanes = buffer.readUInt16LE(26); - this.bitsPerPixel = buffer.readUInt16LE(28); - this.compression = buffer.readUInt32LE(30); - this.size = buffer.readUInt32LE(34); - this.horizontalResolution = buffer.readInt32LE(38); - this.verticalResolution = buffer.readInt32LE(42); - this.colorCount = buffer.readUInt32LE(46); - this.importantColorCount = buffer.readUInt32LE(50); +} + +function readHeader() { + this.type = this.buffer.toString('utf-8', 0, 2); + this.size = this.buffer.readInt32LE(2); + this.reserved1 = this.buffer.readInt32LE(6); + this.reserved2 = this.buffer.readInt32LE(8); + this.pixelArrayOffset = this.buffer.readInt32LE(10); +} + +function readBitmapHeader() { + this.bitmapHeaderSize = this.buffer.readUInt32LE(14); + + switch (this.bitmapHeaderSize) { + case 40: + readBitmapInfoHeader.call(this, this.buffer); + break; + case 12: + case 16: + case 52: + case 56: + case 108: + case 124: + default: + throw new Error('The bitmap\'s header type is currently unsupported.'); } - - function readColorTable() { - this.colors = []; - this.colorTableOffset = 14 + this.bitmapHeaderSize; - this.colorTableSize = this.colorCount * 4; - - for (let i = this.colorTableOffset; i <= this.colorTableOffset + this.colorTableSize; i += 4) { - let bgraHex = buffer.toString('hex', i, i + 4); - let color = Color.fromBGRAHex(bgraHex); - this.colors.push(color); - } +} + +function readBitmapInfoHeader() { + this.width = this.buffer.readInt32LE(18); + this.height = this.buffer.readInt32LE(22); + this.colorPlanes = this.buffer.readUInt16LE(26); + this.bitsPerPixel = this.buffer.readUInt16LE(28); + this.compression = this.buffer.readUInt32LE(30); + this.size = this.buffer.readUInt32LE(34); + this.horizontalResolution = this.buffer.readInt32LE(38); + this.verticalResolution = this.buffer.readInt32LE(42); + this.colorCount = this.buffer.readUInt32LE(46); + this.importantColorCount = this.buffer.readUInt32LE(50); +} + +function readColorTable() { + this.colors = []; + this.colorTableOffset = 14 + this.bitmapHeaderSize; + this.colorTableSize = this.colorCount * 4; + + for (let i = this.colorTableOffset; i < this.colorTableOffset + this.colorTableSize; i += 4) { + let bgraHex = this.buffer.toString('hex', i, i + 4); + let color = Color.fromBGRAHex(bgraHex); + this.colors.push(color); } - - function readPixelArray() { - this.rowSizeInBytes = Math.ceil(this.bitsPerPixel * this.width / 32) * 4; - this.bytesPerPixel = this.bitsPerPixel / 8; - - this.pixelArray = []; - - for (let row = this.height - 1; row >= 0; row--) { - let pixelRow = []; - let rowOffset = this.pixelArrayByteOffset + row * this.rowSizeInBytes; - - for (let pixel = 0; pixel < this.width; pixel++) { - let pixelOffset = rowOffset + pixel * this.bytesPerPixel; - - if (this.bitsPerPixel < 16) { - let colorIndex = buffer.readUInt8(pixelOffset); - pixelRow.push(this.colors[colorIndex]); - } else { - let bgraHex = buffer.toString('hex', pixelOffset, this.bytesPerPixel); - let color = Color.fromBGRAHex(bgraHex); - pixelRow.push(color); - } +} + +function readPixelArray() { + this.pixelRowSize = Math.ceil(this.bitsPerPixel * this.width / 32) * 4; + this.bytesPerPixel = this.bitsPerPixel / 8; + + this.pixelArray = []; + + for (let row = this.height - 1; row >= 0; row--) { + let pixelRow = []; + let rowOffset = this.pixelArrayOffset + row * this.pixelRowSize; + + for (let pixel = 0; pixel < this.width; pixel++) { + let pixelOffset = rowOffset + pixel * this.bytesPerPixel; + + if (this.bitsPerPixel < 16) { + let colorIndex = this.buffer.readUInt8(pixelOffset); + pixelRow.push(this.colors[colorIndex]); + } else { + let bgraHex = this.buffer.toString('hex', pixelOffset, this.bytesPerPixel); + let color = Color.fromBGRAHex(bgraHex); + pixelRow.push(color); } - - this.pixelArray.push(pixelRow); } + + this.pixelArray.push(pixelRow); } } \ No newline at end of file diff --git a/bitmap-autobots/lib/color.js b/bitmap-autobots/lib/color.js index 7f7623b..ee4a60a 100644 --- a/bitmap-autobots/lib/color.js +++ b/bitmap-autobots/lib/color.js @@ -67,16 +67,16 @@ function Color(red, green, blue, alpha = 0) { return new Color(result, result, result, this.alpha); }; +} - function toPaddedHex(channel) { - channel = channel.toString(16); - - if (channel.length === 1) { - return '0' + channel; - } +function toPaddedHex(channel) { + channel = channel.toString(16); - return channel; + if (channel.length === 1) { + return '0' + channel; } + + return channel; } Color.fromRGBAHex = function(rgbaHex) { diff --git a/bitmap-autobots/package.json b/bitmap-autobots/package.json index 4276148..6213820 100644 --- a/bitmap-autobots/package.json +++ b/bitmap-autobots/package.json @@ -13,5 +13,9 @@ }, "keywords": [], "author": "", - "license": "MIT" + "license": "MIT", + "devDependencies": { + "chai": "^4.1.0", + "mocha": "^3.4.2" + } } diff --git a/bitmap-autobots/test/bitmap-test.js b/bitmap-autobots/test/bitmap-test.js index e69de29..9f86882 100644 --- a/bitmap-autobots/test/bitmap-test.js +++ b/bitmap-autobots/test/bitmap-test.js @@ -0,0 +1,71 @@ +'use strict'; + +const fs = require('fs'); +const expect = require('chai').expect; +const Bitmap = require('./../lib/bitmap.js'); + +var bitmap; + +describe('Bitmap', function() { + before(done => { + fs.readFile(`${__dirname}/../assets/palette-bitmap.bmp`, function(err, buffer) { + if (err) throw err; + bitmap = new Bitmap(buffer); + done(); + }); + }); + + describe('#()', () => { + it('should throw error if no buffer provided.', () => { + expect(() => new Bitmap()).to.throw(ReferenceError); + }); + }); + + describe('#readHeader()', () => { + it('should return the correct property values.', () => { + expect(bitmap).to.have.property('type', 'BM'); + expect(bitmap).to.have.property('size', 10000); + expect(bitmap).to.have.property('reserved1', 0); + expect(bitmap).to.have.property('reserved2', 70647808); + expect(bitmap).to.have.property('pixelArrayOffset', 1078); + }); + }); + + describe('#readBitmapHeader()', () => { + it('should return the correct property values.', () => { + expect(bitmap).to.have.property('bitmapHeaderSize', 40); + }); + }); + + describe('#readBitmapInfoHeader()', () => { + it('should return the correct property values.', () => { + expect(bitmap).to.have.property('width', 100); + expect(bitmap).to.have.property('height', 100); + expect(bitmap).to.have.property('colorPlanes', 1); + expect(bitmap).to.have.property('bitsPerPixel', 8); + expect(bitmap).to.have.property('compression', 0); + expect(bitmap).to.have.property('horizontalResolution', 2834); + expect(bitmap).to.have.property('verticalResolution', 2834); + expect(bitmap).to.have.property('colorCount', 256); + expect(bitmap).to.have.property('importantColorCount', 256); + }); + }); + + describe('#readColorTable()', () => { + it('should return the correct property values.', () => { + expect(bitmap).to.have.property('colors'); + expect(bitmap.colors.length).to.equal(256); + expect(bitmap).to.have.property('colorTableOffset', 54); + expect(bitmap).to.have.property('colorTableSize', 1024); + }); + }); + + describe('#readPixelArray()', () => { + it('should return the correct property values.', () => { + expect(bitmap).to.have.property('pixelRowSize', 100); + expect(bitmap).to.have.property('bytesPerPixel', 1); + expect(bitmap).to.have.property('pixelArray'); + expect(bitmap.pixelArray.length).to.equal(100); + }); + }); +}); From 328408e481f56c7f7a84471f9e79d882eb8ed86d Mon Sep 17 00:00:00 2001 From: Nathan Harrenstein Date: Sat, 22 Jul 2017 23:47:39 -0700 Subject: [PATCH 16/22] Adds color test. --- bitmap-autobots/lib/bitmap-transformer.js | 2 +- bitmap-autobots/lib/bitmap.js | 16 ++- bitmap-autobots/lib/color.js | 116 ++++++++++++---------- bitmap-autobots/test/color-test.js | 113 +++++++++++++++++++++ 4 files changed, 181 insertions(+), 66 deletions(-) create mode 100644 bitmap-autobots/test/color-test.js diff --git a/bitmap-autobots/lib/bitmap-transformer.js b/bitmap-autobots/lib/bitmap-transformer.js index 0e5c036..71cf451 100644 --- a/bitmap-autobots/lib/bitmap-transformer.js +++ b/bitmap-autobots/lib/bitmap-transformer.js @@ -39,7 +39,7 @@ function BitmapTransformer(bitmap) { }; this.writePixel = function(x, y, color) { - let pixelOffset = bitmap.pixelArrayOffset + (bitmap.height - 1 - y) * bitmap.rowSizeInBytes + x * bitmap.bytesPerPixel; + let pixelOffset = bitmap.pixelArrayOffset + (bitmap.height - 1 - y) * bitmap.pixelRowSize + x * bitmap.bytesPerPixel; let colorIndex = bitmap.colors.indexOf(color); if (colorIndex === -1) { diff --git a/bitmap-autobots/lib/bitmap.js b/bitmap-autobots/lib/bitmap.js index af1fefb..1f93de6 100644 --- a/bitmap-autobots/lib/bitmap.js +++ b/bitmap-autobots/lib/bitmap.js @@ -13,16 +13,12 @@ function Bitmap(buffer) { readBitmapHeader.call(this); readColorTable.call(this); readPixelArray.call(this); - - this.clone = function() { - return new Bitmap(Buffer.from(this.buffer)); - }; - - this.getPixel = function(x, y) { - return this.pixelArray[y][x]; - }; } +Bitmap.prototype.clone = function() { + return new Bitmap(Buffer.from(this.buffer)); +}; + function readHeader() { this.type = this.buffer.toString('utf-8', 0, 2); this.size = this.buffer.readInt32LE(2); @@ -69,7 +65,7 @@ function readColorTable() { for (let i = this.colorTableOffset; i < this.colorTableOffset + this.colorTableSize; i += 4) { let bgraHex = this.buffer.toString('hex', i, i + 4); - let color = Color.fromBGRAHex(bgraHex); + let color = Color.fromBGRAString(bgraHex); this.colors.push(color); } } @@ -92,7 +88,7 @@ function readPixelArray() { pixelRow.push(this.colors[colorIndex]); } else { let bgraHex = this.buffer.toString('hex', pixelOffset, this.bytesPerPixel); - let color = Color.fromBGRAHex(bgraHex); + let color = Color.fromBGRAString(bgraHex); pixelRow.push(color); } } diff --git a/bitmap-autobots/lib/color.js b/bitmap-autobots/lib/color.js index ee4a60a..3a63f77 100644 --- a/bitmap-autobots/lib/color.js +++ b/bitmap-autobots/lib/color.js @@ -3,71 +3,77 @@ module.exports = Color; function Color(red, green, blue, alpha = 0) { + if (typeof red !== 'number') throw new TypeError('red was not a number'); + if (typeof green !== 'number') throw new TypeError('green was not a number'); + if (typeof blue !== 'number') throw new TypeError('blue was not a number'); + + if (red < 0 || red > 255) throw new RangeError('red was out of range'); + if (green < 0 || green > 255) throw new RangeError('green was out of range'); + if (blue < 0 || blue > 255) throw new RangeError('blue was out of range'); + this.red = red; this.green = green; this.blue = blue; this.alpha = alpha; +} - this.getHSL = function() { - let redness = this.red / 255; - let greenness = this.green / 255; - let blueness = this.blue / 255; - - let min = Math.min(redness, greenness, blueness); - let max = Math.max(redness, greenness, blueness); - let difference = max - min; - - this.hue = difference; - this.saturation = difference; - this.lightness = (min + max) / 2; - - if (difference === 0) { - this.saturation = 0; - this.hue = 0; - } else { - this.saturation = difference / (1 - Math.abs(2 * this.lightness - 1)); - switch (max) { - case redness: - this.hue = Math.round(60 * (((greenness - blueness) / difference) % 6)); - break; - case greenness: - this.hue = Math.round(60 * (((blueness - redness) / difference) + 2)); - break; - case blueness: - this.hue = Math.round(60 * (((redness - greenness) / difference) + 4)); - break; - } - - if (this.hue < 0) { - this.hue += 360; - } +Color.prototype.getHSL = function() { + let redness = this.red / 255; + let greenness = this.green / 255; + let blueness = this.blue / 255; + + let min = Math.min(redness, greenness, blueness); + let max = Math.max(redness, greenness, blueness); + let difference = max - min; + + this.hue = difference; + this.saturation = difference; + this.lightness = (min + max) / 2; + + if (difference === 0) { + this.saturation = 0; + this.hue = 0; + } + else { + this.saturation = difference / (1 - Math.abs(2 * this.lightness - 1)); + + if (max === redness) { + this.hue = Math.round(60 * (((greenness - blueness) / difference) % 6)); + } else if (max === greenness) { + this.hue = Math.round(60 * (((blueness - redness) / difference) + 2)); + } else if (max === blueness) { + this.hue = Math.round(60 * (((redness - greenness) / difference) + 4)); } - }; - this.toBGRAString = function() { - return toPaddedHex(blue) + toPaddedHex(green) + toPaddedHex(red) + toPaddedHex(alpha); - }; + if (this.hue < 0) { + this.hue += 360; + } + } +}; - this.toRGBAString = function() { - return toPaddedHex(red) + toPaddedHex(green) + toPaddedHex(blue) + toPaddedHex(alpha); - }; +Color.prototype.toBGRAString = function() { + return toPaddedHex(this.blue) + toPaddedHex(this.green) + toPaddedHex(this.red) + toPaddedHex(this.alpha); +}; - this.toGrayscale = function() { - let average = Math.round((this.red + this.green + this.blue) / 3); - return new Color(average, average, average, this.alpha); - }; +Color.prototype.toRGBAString = function() { + return toPaddedHex(this.red) + toPaddedHex(this.green) + toPaddedHex(this.blue) + toPaddedHex(this.alpha); +}; - this.toInverse = function() { - return new Color(255 - this.red, 255 - this.green, 255 - this.blue, this.alpha); - }; +Color.prototype.toGrayscale = function() { + let average = Math.round((this.red + this.green + this.blue) / 3); + return new Color(average, average, average, this.alpha); +}; - this.toBlackAndWhite = function() { - let average = Math.round((this.red + this.green + this.blue) / 3); - let result = average < 128 ? 0 : 255; +Color.prototype.toInverse = function() { + return new Color(255 - this.red, 255 - this.green, 255 - this.blue, this.alpha); +}; - return new Color(result, result, result, this.alpha); - }; -} +Color.prototype.toBlackAndWhite = function() { + let average = Math.round((this.red + this.green + this.blue) / 3); + let result = average < 128 ? 0 : 255; + + return new Color(result, result, result, this.alpha); +}; function toPaddedHex(channel) { channel = channel.toString(16); @@ -79,7 +85,7 @@ function toPaddedHex(channel) { return channel; } -Color.fromRGBAHex = function(rgbaHex) { +Color.fromRGBAString = function(rgbaHex) { let red = parseInt(rgbaHex.slice(0, 2), 16); let green = parseInt(rgbaHex.slice(2, 4), 16); let blue = parseInt(rgbaHex.slice(4, 6), 16); @@ -88,7 +94,7 @@ Color.fromRGBAHex = function(rgbaHex) { return new Color(red, green, blue, alpha); }; -Color.fromBGRAHex = function(rgbaHex) { +Color.fromBGRAString = function(rgbaHex) { let blue = parseInt(rgbaHex.slice(0, 2), 16); let green = parseInt(rgbaHex.slice(2, 4), 16); let red = parseInt(rgbaHex.slice(4, 6), 16); diff --git a/bitmap-autobots/test/color-test.js b/bitmap-autobots/test/color-test.js new file mode 100644 index 0000000..b2cd84d --- /dev/null +++ b/bitmap-autobots/test/color-test.js @@ -0,0 +1,113 @@ +'use strict'; + +const expect = require('chai').expect; +const Color = require('./../lib/color.js'); + +var color = new Color(10, 20, 30, 40); + +describe('Color', function() { + describe('#()', () => { + it('should throw errors if the input values are not numbers.', () => { + expect(() => new Color(0, 0)).to.throw(TypeError); + expect(() => new Color(0)).to.throw(TypeError); + expect(() => new Color()).to.throw(TypeError); + }); + + it('should throw errors if the input values are out of range.', () => { + expect(() => new Color(256, 0, 0)).to.throw(RangeError); + expect(() => new Color(0, 256, 0)).to.throw(RangeError); + expect(() => new Color(0, 0, 256)).to.throw(RangeError); + }); + + it('should return the correct property values.', () => { + expect(color).to.have.property('red', 10); + expect(color).to.have.property('green', 20); + expect(color).to.have.property('blue', 30); + expect(color).to.have.property('alpha', 40); + }); + }); + + describe('#getHSL()', () => { + it('should return the correct property values.', () => { + color.getHSL(); + expect(color).to.have.property('hue', 210); + expect(color).to.have.property('saturation', 0.5); + expect(color).to.have.property('lightness', 0.0784313725490196); + expect(color).to.have.property('alpha', 40); + }); + }); + + describe('#toBGRAString()', () => { + it('should return the correct hex value.', () => { + let bgraString = color.toBGRAString(); + expect(bgraString).to.equal('1e140a28'); + }); + }); + + describe('#toRGBAString()', () => { + it('should return the correct hex value.', () => { + let rgbaString = color.toRGBAString(); + expect(rgbaString).to.equal('0a141e28'); + }); + }); + + describe('#toGreyscale()', () => { + it('should return the correct property values.', () => { + let grayscale = color.toGrayscale(); + expect(grayscale.red).to.equal(20); + expect(grayscale.green).to.equal(20); + expect(grayscale.blue).to.equal(20); + }); + }); + + describe('#toInverse()', () => { + it('should return the correct property values.', () => { + let inverse = color.toInverse(); + expect(inverse.red).to.equal(245); + expect(inverse.green).to.equal(235); + expect(inverse.blue).to.equal(225); + }); + }); + + describe('#toBlackAndWhite()', () => { + it('should return the correct property values.', () => { + let blackAndWhite = color.toBlackAndWhite(); + expect(blackAndWhite.red).to.equal(0); + expect(blackAndWhite.green).to.equal(0); + expect(blackAndWhite.blue).to.equal(0); + }); + }); + + describe('#fromRGBAString()', () => { + it('should return the correct property values.', () => { + let rgba = Color.fromRGBAString('0a141e28'); + expect(rgba.red).to.equal(10); + expect(rgba.green).to.equal(20); + expect(rgba.blue).to.equal(30); + expect(rgba.alpha).to.equal(40); + }); + }); + + describe('#fromBGRAString()', () => { + it('should return the correct property values.', () => { + let bgra = Color.fromBGRAString('1e140a28'); + expect(bgra.red).to.equal(10); + expect(bgra.green).to.equal(20); + expect(bgra.blue).to.equal(30); + expect(bgra.alpha).to.equal(40); + }); + }); + + describe('#fromHSLA()', () => { + it('should return the correct property values.', () => { + let hsla = Color.fromHSLA(210, 0.5, 0.0784313725490196, 40); + expect(hsla.red).to.equal(10); + expect(hsla.green).to.equal(20); + expect(hsla.blue).to.equal(30); + expect(hsla.alpha).to.equal(40); + expect(hsla.hue).to.equal(210); + expect(hsla.saturation).to.equal(0.5); + expect(hsla.lightness).to.equal(0.0784313725490196); + }); + }); +}); \ No newline at end of file From 4ba45129ad0c149c43c0df3e2472bbc54dbd0f8f Mon Sep 17 00:00:00 2001 From: Nathan Harrenstein Date: Sun, 23 Jul 2017 00:03:34 -0700 Subject: [PATCH 17/22] Adds bitmap transformer test. --- bitmap-autobots/lib/bitmap-transformer.js | 74 ++++++++++--------- .../test/bitmap-transformer-test.js | 12 +++ 2 files changed, 51 insertions(+), 35 deletions(-) diff --git a/bitmap-autobots/lib/bitmap-transformer.js b/bitmap-autobots/lib/bitmap-transformer.js index 71cf451..84d69f7 100644 --- a/bitmap-autobots/lib/bitmap-transformer.js +++ b/bitmap-autobots/lib/bitmap-transformer.js @@ -6,49 +6,53 @@ const Color = require('./color.js'); module.exports = BitmapTransformer; function BitmapTransformer(bitmap) { - this.bitmap = bitmap; + if (!(bitmap instanceof Bitmap)) { + throw new TypeError('The input is not a bitmap.'); + } + this.bitmap = bitmap; +} - this.addColor = function(color) { - let uniqueColors = {}; +BitmapTransformer.prototype.addColor = function(color) { + let uniqueColors = {}; - for (let i = 0; i < bitmap.colors.length; i++) { - let uniqueColor = uniqueColors[bitmap.colors[i].toRGBAString()]; + for (let i = 0; i < this.bitmap.colors.length; i++) { + let uniqueColor = uniqueColors[this.bitmap.colors[i].toRGBAString()]; - if (uniqueColor) { - bitmap.colors[i] = color; - return i; - } - else { - uniqueColors[color.toRGBAString()] = bitmap.colors[i]; - } - } - }; - - this.writeColors = function() { - let bgraColors = bitmap.colors.map(c => c.toBGRAString()).join(''); - bitmap.buffer.write(bgraColors, bitmap.colorTableOffset, bitmap.colorTableSize, 'hex'); - }; - - this.writePixels = function() { - for (var y = 0; y < bitmap.height; y++) { - for (var x = 0; x < bitmap.width; x++) { - this.writePixel(x, y, bitmap.pixelArray[y][x]); - } + if (uniqueColor) { + this.bitmap.colors[i] = color; + return i; + } + else { + uniqueColors[color.toRGBAString()] = this.bitmap.colors[i]; } - }; + } +}; - this.writePixel = function(x, y, color) { - let pixelOffset = bitmap.pixelArrayOffset + (bitmap.height - 1 - y) * bitmap.pixelRowSize + x * bitmap.bytesPerPixel; - let colorIndex = bitmap.colors.indexOf(color); +BitmapTransformer.prototype.writeColors = function() { + let bgraColors = this.bitmap.colors.map(c => c.toBGRAString()).join(''); + this.bitmap.buffer.write(bgraColors, this.bitmap.colorTableOffset, this.bitmap.colorTableSize, 'hex'); +}; - if (colorIndex === -1) { - colorIndex = this.addColor(color); - } +BitmapTransformer.prototype.writePixels = function() { + for (var y = 0; y < this.bitmap.height; y++) { + for (var x = 0; x < this.bitmap.width; x++) { + this.writePixel(x, y, this.bitmap.pixelArray[y][x]); + } + } +}; + +BitmapTransformer.prototype.writePixel = function(x, y, color) { + let pixelOffset = this.bitmap.pixelArrayOffset + (this.bitmap.height - 1 - y) * this.bitmap.pixelRowSize + x * this.bitmap.bytesPerPixel; + let colorIndex = this.bitmap.colors.indexOf(color); + + if (colorIndex === -1) { + colorIndex = this.addColor(color); + } + + this.bitmap.buffer.writeUInt8(colorIndex, pixelOffset); +}; - bitmap.buffer.writeUInt8(colorIndex, pixelOffset); - }; -} Bitmap.prototype.toGrayscale = function() { let clone = this.clone(); diff --git a/bitmap-autobots/test/bitmap-transformer-test.js b/bitmap-autobots/test/bitmap-transformer-test.js index e69de29..77cb400 100644 --- a/bitmap-autobots/test/bitmap-transformer-test.js +++ b/bitmap-autobots/test/bitmap-transformer-test.js @@ -0,0 +1,12 @@ +'use strict'; + +const expect = require('chai').expect; +const BitmapTransformer = require('./../lib/bitmap-transformer.js'); + +describe('Bitmap Transformer', function() { + describe('#()', () => { + it('should throw errors if the input is not a bitmap.', () => { + expect(() => new BitmapTransformer()).to.throw(TypeError); + }); + }); +}); From f5ede669654f97ee262f8431ac62286c3e6e5203 Mon Sep 17 00:00:00 2001 From: Nathan Harrenstein Date: Sun, 23 Jul 2017 19:15:37 -0700 Subject: [PATCH 18/22] Moves color transforms to separate module and reads all bitmap headers. --- bitmap-autobots/index.js | 3 +- bitmap-autobots/lib/bitmap-transformer.js | 131 ---------------- bitmap-autobots/lib/bitmap-transforms.js | 145 ++++++++++++++++++ bitmap-autobots/lib/bitmap.js | 94 +++++++++--- bitmap-autobots/lib/color-transforms.js | 25 +++ bitmap-autobots/lib/color.js | 16 -- bitmap-autobots/test/color-test.js | 27 ---- bitmap-autobots/test/color-transforms-test.js | 36 +++++ 8 files changed, 284 insertions(+), 193 deletions(-) create mode 100644 bitmap-autobots/lib/bitmap-transforms.js create mode 100644 bitmap-autobots/lib/color-transforms.js create mode 100644 bitmap-autobots/test/color-transforms-test.js diff --git a/bitmap-autobots/index.js b/bitmap-autobots/index.js index c0a0562..7e44cda 100644 --- a/bitmap-autobots/index.js +++ b/bitmap-autobots/index.js @@ -2,7 +2,7 @@ const fs = require('fs'); const Bitmap = require('./lib/bitmap.js'); -require('./lib/bitmap-transformer.js'); +require('./lib/bitmap-transforms.js'); let checkOutputDirectoryExists = function() { return new Promise(function(resolve, reject) { @@ -51,6 +51,7 @@ checkOutputDirectoryExists() .then(loadBitmap, console.error) .then(bitmap => createOutput('black-and-white', bitmap, () => bitmap.toBlackAndWhite()), console.error) .then(bitmap => createOutput('grayscale', bitmap, () => bitmap.toGrayscale()), console.error) + .then(bitmap => createOutput('sepia', bitmap, () => bitmap.toSepia()), console.error) .then(bitmap => createOutput('inverse-colors', bitmap, () => bitmap.toInverse()), console.error) .then(bitmap => createOutput('flipped-horizontally', bitmap, () => bitmap.flipHorizontally()), console.error) .then(bitmap => createOutput('flipped-vertically', bitmap, () => bitmap.flipVertically()), console.error) diff --git a/bitmap-autobots/lib/bitmap-transformer.js b/bitmap-autobots/lib/bitmap-transformer.js index 84d69f7..b34c6a0 100644 --- a/bitmap-autobots/lib/bitmap-transformer.js +++ b/bitmap-autobots/lib/bitmap-transformer.js @@ -1,7 +1,6 @@ 'use strict'; const Bitmap = require('./bitmap.js'); -const Color = require('./color.js'); module.exports = BitmapTransformer; @@ -51,134 +50,4 @@ BitmapTransformer.prototype.writePixel = function(x, y, color) { } this.bitmap.buffer.writeUInt8(colorIndex, pixelOffset); -}; - - -Bitmap.prototype.toGrayscale = function() { - let clone = this.clone(); - let bitmapTransformer = new BitmapTransformer(clone); - - clone.colors = clone.colors.map(c => c.toGrayscale()); - - bitmapTransformer.writeColors(); - return clone; -}; - -Bitmap.prototype.toInverse = function() { - let clone = this.clone(); - let bitmapTransformer = new BitmapTransformer(clone); - - clone.colors = clone.colors.map(c => c.toInverse()); - - bitmapTransformer.writeColors(); - return clone; -}; - -Bitmap.prototype.toBlackAndWhite = function() { - let clone = this.clone(); - let bitmapTransformer = new BitmapTransformer(clone); - - clone.colors = clone.colors.map(c => c.toBlackAndWhite()); - - bitmapTransformer.writeColors(); - return clone; -}; - -Bitmap.prototype.flipHorizontally = function() { - let clone = this.clone(); - let bitmapTransformer = new BitmapTransformer(clone); - - for (var i = 0; i < clone.height; i++) { - clone.pixelArray[i] = clone.pixelArray[i].reverse(); - } - - bitmapTransformer.writePixels(); - return clone; -}; - -Bitmap.prototype.flipVertically = function() { - let clone = this.clone(); - let bitmapTransformer = new BitmapTransformer(clone); - - clone.pixelArray = clone.pixelArray.reverse(); - - bitmapTransformer.writePixels(); - return clone; -}; - -Bitmap.prototype.rotateClockwise = function() { - let clone = this.clone(); - let bitmapTransformer = new BitmapTransformer(clone); - - for (var y = 0; y < clone.height; y++) { - for (var x = 0; x < clone.width; x++) { - bitmapTransformer.writePixel(this.height - 1 - y, x, this.pixelArray[y][x]); - } - } - - return clone; -}; - -Bitmap.prototype.rotateCounterclockwise = function() { - let clone = this.clone(); - let bitmapTransformer = new BitmapTransformer(clone); - - for (var y = 0; y < clone.height; y++) { - for (var x = 0; x < clone.width; x++) { - bitmapTransformer.writePixel(y, this.width - 1 - x, this.pixelArray[y][x]); - } - } - - return clone; -}; - -Bitmap.prototype.shiftHue = function(degrees) { - let clone = this.clone(); - let bitmapTransformer = new BitmapTransformer(clone); - - clone.colors = clone.colors.map(c => { - c.getHSL(); - let hue = c.hue + degrees; - - if (Math.abs(hue) > 360) { - hue *= (1 / (hue / 360)); - } - - if (hue < 0) { - hue += 360; - } - - return Color.fromHSLA(hue, c.saturation, c.lightness, c.alpha); - }); - - bitmapTransformer.writeColors(); - return clone; -}; - -Bitmap.prototype.shiftSaturation = function(percentage) { - let clone = this.clone(); - let bitmapTransformer = new BitmapTransformer(clone); - - clone.colors = clone.colors.map(c => { - c.getHSL(); - let saturation = Math.min(1, Math.max(0, c.saturation + percentage / 100)); - return Color.fromHSLA(c.hue, saturation, c.lightness, c.alpha); - }); - - bitmapTransformer.writeColors(); - return clone; -}; - -Bitmap.prototype.shiftLightness = function(percentage) { - let clone = this.clone(); - let bitmapTransformer = new BitmapTransformer(clone); - - clone.colors = clone.colors.map(c => { - c.getHSL(); - let lightness = Math.min(1, Math.max(0, c.lightness + percentage / 100)); - return Color.fromHSLA(c.hue, c.saturation, lightness, c.alpha); - }); - - bitmapTransformer.writeColors(); - return clone; }; \ No newline at end of file diff --git a/bitmap-autobots/lib/bitmap-transforms.js b/bitmap-autobots/lib/bitmap-transforms.js new file mode 100644 index 0000000..f988a04 --- /dev/null +++ b/bitmap-autobots/lib/bitmap-transforms.js @@ -0,0 +1,145 @@ +'use strict'; + +const Color = require('./color.js'); +require('./color-transforms.js'); +const Bitmap = require('./bitmap.js'); +const BitmapTransformer = require('./bitmap-transformer.js'); + +Bitmap.prototype.toInverse = function() { + let clone = this.clone(); + let bitmapTransformer = new BitmapTransformer(clone); + + clone.colors = clone.colors.map(c => c.toInverse()); + + bitmapTransformer.writeColors(); + return clone; +}; + +Bitmap.prototype.toSepia = function() { + let clone = this.clone(); + let bitmapTransformer = new BitmapTransformer(clone); + + clone.colors = clone.colors.map(c => c.toSepia()); + + bitmapTransformer.writeColors(); + return clone; +}; + +Bitmap.prototype.toGrayscale = function() { + let clone = this.clone(); + let bitmapTransformer = new BitmapTransformer(clone); + + clone.colors = clone.colors.map(c => c.toGrayscale()); + + bitmapTransformer.writeColors(); + return clone; +}; + +Bitmap.prototype.toBlackAndWhite = function() { + let clone = this.clone(); + let bitmapTransformer = new BitmapTransformer(clone); + + clone.colors = clone.colors.map(c => c.toBlackAndWhite()); + + bitmapTransformer.writeColors(); + return clone; +}; + +Bitmap.prototype.flipHorizontally = function() { + let clone = this.clone(); + let bitmapTransformer = new BitmapTransformer(clone); + + for (var i = 0; i < clone.height; i++) { + clone.pixelArray[i] = clone.pixelArray[i].reverse(); + } + + bitmapTransformer.writePixels(); + return clone; +}; + +Bitmap.prototype.flipVertically = function() { + let clone = this.clone(); + let bitmapTransformer = new BitmapTransformer(clone); + + clone.pixelArray = clone.pixelArray.reverse(); + + bitmapTransformer.writePixels(); + return clone; +}; + +Bitmap.prototype.rotateClockwise = function() { + let clone = this.clone(); + let bitmapTransformer = new BitmapTransformer(clone); + + for (var y = 0; y < clone.height; y++) { + for (var x = 0; x < clone.width; x++) { + bitmapTransformer.writePixel(this.height - 1 - y, x, this.pixelArray[y][x]); + } + } + + return clone; +}; + +Bitmap.prototype.rotateCounterclockwise = function() { + let clone = this.clone(); + let bitmapTransformer = new BitmapTransformer(clone); + + for (var y = 0; y < clone.height; y++) { + for (var x = 0; x < clone.width; x++) { + bitmapTransformer.writePixel(y, this.width - 1 - x, this.pixelArray[y][x]); + } + } + + return clone; +}; + +Bitmap.prototype.shiftHue = function(degrees) { + let clone = this.clone(); + let bitmapTransformer = new BitmapTransformer(clone); + + clone.colors = clone.colors.map(c => { + c.getHSL(); + let hue = c.hue + degrees; + + if (Math.abs(hue) > 360) { + hue *= (1 / (hue / 360)); + } + + if (hue < 0) { + hue += 360; + } + + return Color.fromHSLA(hue, c.saturation, c.lightness, c.alpha); + }); + + bitmapTransformer.writeColors(); + return clone; +}; + +Bitmap.prototype.shiftSaturation = function(percentage) { + let clone = this.clone(); + let bitmapTransformer = new BitmapTransformer(clone); + + clone.colors = clone.colors.map(c => { + c.getHSL(); + let saturation = Math.min(1, Math.max(0, c.saturation + percentage / 100)); + return Color.fromHSLA(c.hue, saturation, c.lightness, c.alpha); + }); + + bitmapTransformer.writeColors(); + return clone; +}; + +Bitmap.prototype.shiftLightness = function(percentage) { + let clone = this.clone(); + let bitmapTransformer = new BitmapTransformer(clone); + + clone.colors = clone.colors.map(c => { + c.getHSL(); + let lightness = Math.min(1, Math.max(0, c.lightness + percentage / 100)); + return Color.fromHSLA(c.hue, c.saturation, lightness, c.alpha); + }); + + bitmapTransformer.writeColors(); + return clone; +}; \ No newline at end of file diff --git a/bitmap-autobots/lib/bitmap.js b/bitmap-autobots/lib/bitmap.js index 1f93de6..6419a71 100644 --- a/bitmap-autobots/lib/bitmap.js +++ b/bitmap-autobots/lib/bitmap.js @@ -30,32 +30,90 @@ function readHeader() { function readBitmapHeader() { this.bitmapHeaderSize = this.buffer.readUInt32LE(14); - switch (this.bitmapHeaderSize) { - case 40: - readBitmapInfoHeader.call(this, this.buffer); - break; - case 12: - case 16: - case 52: - case 56: - case 108: - case 124: - default: - throw new Error('The bitmap\'s header type is currently unsupported.'); + if (this.bitmapHeaderSize === 12) { + readBitmapCoreHeader.call(this); + return; } -} -function readBitmapInfoHeader() { - this.width = this.buffer.readInt32LE(18); - this.height = this.buffer.readInt32LE(22); this.colorPlanes = this.buffer.readUInt16LE(26); this.bitsPerPixel = this.buffer.readUInt16LE(28); this.compression = this.buffer.readUInt32LE(30); this.size = this.buffer.readUInt32LE(34); - this.horizontalResolution = this.buffer.readInt32LE(38); - this.verticalResolution = this.buffer.readInt32LE(42); this.colorCount = this.buffer.readUInt32LE(46); this.importantColorCount = this.buffer.readUInt32LE(50); + + if (this.bitmapHeaderSize === 16 || this.bitmapHeaderSize === 64) { + readOS22BitmapHeader.call(this); + return; + } + + this.width = this.buffer.readInt32LE(18); + this.height = this.buffer.readInt32LE(22); + this.horizontalResolution = this.buffer.readInt32LE(38); + this.verticalResolution = this.buffer.readInt32LE(42); + + if (this.bitmapHeaderSize === 40) { + return; + } + + this.redMask = this.buffer.readUInt32LE(54); + this.greenMask = this.buffer.readUInt32LE(58); + this.blueMask = this.buffer.readUInt32LE(62); + this.alphaMask = this.buffer.readUInt32LE(66); + this.colorSpaceType = this.buffer.readUInt32LE(70); + this.endpoints = getCIEXYZTriple(this.buffer.slice(74, 110)); + this.gammaRed = this.buffer.readUInt32LE(110); + this.gammaGreen = this.buffer.readUInt32LE(114); + this.gammaBlue = this.buffer.readUInt32LE(118); + this.intent = this.buffer.readUInt32LE(122); + this.profileData = this.buffer.readUInt32LE(126); + this.profileSize = this.buffer.readUInt32LE(130); + this.reserved = this.buffer.readUInt32LE(134); +} + +function readBitmapCoreHeader() { + this.width = this.buffer.readUInt16LE(18); + this.height = this.buffer.readUInt16LE(20); + this.colorPlanes = this.buffer.readUInt16LE(22); + this.bitsPerPixel = this.buffer.readUInt16LE(24); +} + +function readOS22BitmapHeader() { + this.width = this.buffer.readUInt32LE(18); + this.height = this.buffer.readUInt32LE(22); + this.horizontalResolution = this.buffer.readUInt32LE(38); + this.verticalResolution = this.buffer.readUInt32LE(42); + + let short = this.bitmapHeaderSize == 16; + + this.resolutionUnit = short ? 0 : this.buffer.readUInt16LE(54); + this.reserved = short ? 0 : this.buffer.readUInt16LE(56); + this.orientation = short ? 0 : this.buffer.readUInt16LE(58); + this.halftoning = short ? 0 : this.buffer.readUInt16LE(60); + this.halftoneSize1 = short ? 0 : this.buffer.readUInt32LE(62); + this.halftoneSize2 = short ? 0 : this.buffer.readUInt32LE(66); + this.colorSpace = short ? 0 : this.buffer.readUInt32LE(70); + this.appData = short ? 0 : this.buffer.readUInt32LE(74); +} + +function getCIEXYZTriple(buffer) { + let red = new CIEXYZ(buffer.readFloatLE(0), buffer.readFloatLE(4), buffer.readFloatLE(8)); + let green = new CIEXYZ(buffer.readFloatLE(12), buffer.readFloatLE(16), buffer.readFloatLE(20)); + let blue = new CIEXYZ(buffer.readFloatLE(24), buffer.readFloatLE(28), buffer.readFloatLE(32)); + + return new CIEXYZTriple(red, green, blue); +} + +function CIEXYZ(x, y, z) { + this.x = x; + this.y = y; + this.z = z; +} + +function CIEXYZTriple(red, green, blue) { + this.red = red; + this.green = green; + this.blue = blue; } function readColorTable() { diff --git a/bitmap-autobots/lib/color-transforms.js b/bitmap-autobots/lib/color-transforms.js new file mode 100644 index 0000000..d01b25c --- /dev/null +++ b/bitmap-autobots/lib/color-transforms.js @@ -0,0 +1,25 @@ +'use strict'; + +const Color = require('./color'); + +Color.prototype.toInverse = function() { + return new Color(255 - this.red, 255 - this.green, 255 - this.blue, this.alpha); +}; + +Color.prototype.toSepia = function() { + let red = 0.393 * this.red + 0.769 * this.green + 0.189 * this.blue; + let green = 0.349 * this.red + 0.686 * this.green + 0.168 * this.blue; + let blue = 0.272 * this.red + 0.534 * this.green + 0.131 * this.blue; + return new Color(Math.min(Math.round(red), 255), Math.min(Math.round(green), 255), Math.min(Math.round(blue), 255)); +}; + +Color.prototype.toGrayscale = function() { + let average = Math.round((this.red + this.green + this.blue) / 3); + return new Color(average, average, average, this.alpha); +}; + +Color.prototype.toBlackAndWhite = function() { + let average = Math.round((this.red + this.green + this.blue) / 3); + let result = average < 128 ? 0 : 255; + return new Color(result, result, result, this.alpha); +}; \ No newline at end of file diff --git a/bitmap-autobots/lib/color.js b/bitmap-autobots/lib/color.js index 3a63f77..fa237cc 100644 --- a/bitmap-autobots/lib/color.js +++ b/bitmap-autobots/lib/color.js @@ -59,22 +59,6 @@ Color.prototype.toRGBAString = function() { return toPaddedHex(this.red) + toPaddedHex(this.green) + toPaddedHex(this.blue) + toPaddedHex(this.alpha); }; -Color.prototype.toGrayscale = function() { - let average = Math.round((this.red + this.green + this.blue) / 3); - return new Color(average, average, average, this.alpha); -}; - -Color.prototype.toInverse = function() { - return new Color(255 - this.red, 255 - this.green, 255 - this.blue, this.alpha); -}; - -Color.prototype.toBlackAndWhite = function() { - let average = Math.round((this.red + this.green + this.blue) / 3); - let result = average < 128 ? 0 : 255; - - return new Color(result, result, result, this.alpha); -}; - function toPaddedHex(channel) { channel = channel.toString(16); diff --git a/bitmap-autobots/test/color-test.js b/bitmap-autobots/test/color-test.js index b2cd84d..6c5936d 100644 --- a/bitmap-autobots/test/color-test.js +++ b/bitmap-autobots/test/color-test.js @@ -51,33 +51,6 @@ describe('Color', function() { }); }); - describe('#toGreyscale()', () => { - it('should return the correct property values.', () => { - let grayscale = color.toGrayscale(); - expect(grayscale.red).to.equal(20); - expect(grayscale.green).to.equal(20); - expect(grayscale.blue).to.equal(20); - }); - }); - - describe('#toInverse()', () => { - it('should return the correct property values.', () => { - let inverse = color.toInverse(); - expect(inverse.red).to.equal(245); - expect(inverse.green).to.equal(235); - expect(inverse.blue).to.equal(225); - }); - }); - - describe('#toBlackAndWhite()', () => { - it('should return the correct property values.', () => { - let blackAndWhite = color.toBlackAndWhite(); - expect(blackAndWhite.red).to.equal(0); - expect(blackAndWhite.green).to.equal(0); - expect(blackAndWhite.blue).to.equal(0); - }); - }); - describe('#fromRGBAString()', () => { it('should return the correct property values.', () => { let rgba = Color.fromRGBAString('0a141e28'); diff --git a/bitmap-autobots/test/color-transforms-test.js b/bitmap-autobots/test/color-transforms-test.js new file mode 100644 index 0000000..0239ea1 --- /dev/null +++ b/bitmap-autobots/test/color-transforms-test.js @@ -0,0 +1,36 @@ +'use strict'; + +const expect = require('chai').expect; +const Color = require('./../lib/color.js'); +require('./../lib/color-transforms.js'); + +var color = new Color(10, 20, 30, 40); + +describe('Color', function() { + describe('#toGreyscale()', () => { + it('should return the correct property values.', () => { + let grayscale = color.toGrayscale(); + expect(grayscale.red).to.equal(20); + expect(grayscale.green).to.equal(20); + expect(grayscale.blue).to.equal(20); + }); + }); + + describe('#toInverse()', () => { + it('should return the correct property values.', () => { + let inverse = color.toInverse(); + expect(inverse.red).to.equal(245); + expect(inverse.green).to.equal(235); + expect(inverse.blue).to.equal(225); + }); + }); + + describe('#toBlackAndWhite()', () => { + it('should return the correct property values.', () => { + let blackAndWhite = color.toBlackAndWhite(); + expect(blackAndWhite.red).to.equal(0); + expect(blackAndWhite.green).to.equal(0); + expect(blackAndWhite.blue).to.equal(0); + }); + }); +}); \ No newline at end of file From d3253afe75cbfecc5cff8c9851246d97e76506cf Mon Sep 17 00:00:00 2001 From: Nathan Harrenstein Date: Sun, 23 Jul 2017 19:39:16 -0700 Subject: [PATCH 19/22] Adds chroma transforms and updated documentation. --- bitmap-autobots/README.md | 24 +++++++++++++++++- bitmap-autobots/index.js | 7 ++++-- bitmap-autobots/lib/bitmap-transforms.js | 31 ++++++++++++++++++++++++ bitmap-autobots/lib/color-transforms.js | 12 +++++++++ 4 files changed, 71 insertions(+), 3 deletions(-) diff --git a/bitmap-autobots/README.md b/bitmap-autobots/README.md index bc041e1..894d053 100644 --- a/bitmap-autobots/README.md +++ b/bitmap-autobots/README.md @@ -1,3 +1,25 @@ # Bitmap Transformer -This project contains three modules which facilitate the transformation of bitmap files. The color module provides support for color manipulation as well as conversion to and from hex strings, the bitmap module parses a bitmap into properties and objects, and the bitmap transformer module provides methods for modifying a bitmap and attaches an assortment of transforms onto the bitmap's prototype. \ No newline at end of file +This project contains five modules which facilitate the transformation of bitmap files: + +**Color**: Handles color calcuations, parsing, and stringifying. +**Color Transforms**: Attaches various color transformations to the Color prototype. +**Bitmap**: Handles parsing of bitmap files. +**Bitmap Transformer**: Exposes methods for manipulating bitmaps. +**Bitmap Transforms**: Attaches various bitmap transformations to the Bitmap prototype. + +The available transformations are: +* Black and White +* Grayscale +* Sepia +* Horizontal Flip +* Vertical Flip +* Color Inversion +* Clockwise Rotation +* Counterclockwise Rotation +* Hue Shift +* Lightness Shift +* Saturation Shift +* Redness Shift +* Greenness Shift +* Blueness Shift \ No newline at end of file diff --git a/bitmap-autobots/index.js b/bitmap-autobots/index.js index 7e44cda..51ec9d4 100644 --- a/bitmap-autobots/index.js +++ b/bitmap-autobots/index.js @@ -52,11 +52,14 @@ checkOutputDirectoryExists() .then(bitmap => createOutput('black-and-white', bitmap, () => bitmap.toBlackAndWhite()), console.error) .then(bitmap => createOutput('grayscale', bitmap, () => bitmap.toGrayscale()), console.error) .then(bitmap => createOutput('sepia', bitmap, () => bitmap.toSepia()), console.error) - .then(bitmap => createOutput('inverse-colors', bitmap, () => bitmap.toInverse()), console.error) + .then(bitmap => createOutput('inverted-colors', bitmap, () => bitmap.toInverse()), console.error) .then(bitmap => createOutput('flipped-horizontally', bitmap, () => bitmap.flipHorizontally()), console.error) .then(bitmap => createOutput('flipped-vertically', bitmap, () => bitmap.flipVertically()), console.error) .then(bitmap => createOutput('rotate-clockwise', bitmap, () => bitmap.rotateClockwise()), console.error) .then(bitmap => createOutput('rotate-counterclockwise', bitmap, () => bitmap.rotateCounterclockwise()), console.error) .then(bitmap => createOutput('shift-hue', bitmap, () => bitmap.shiftHue(60)), console.error) .then(bitmap => createOutput('shift-saturation', bitmap, () => bitmap.shiftSaturation(-30)), console.error) - .then(bitmap => createOutput('shift-lightness', bitmap, () => bitmap.shiftLightness(20)), console.error); \ No newline at end of file + .then(bitmap => createOutput('shift-lightness', bitmap, () => bitmap.shiftLightness(20)), console.error) + .then(bitmap => createOutput('shift-redness', bitmap, () => bitmap.shiftRedness(5)), console.error) + .then(bitmap => createOutput('shift-greenness', bitmap, () => bitmap.shiftGreenness(5)), console.error) + .then(bitmap => createOutput('shift-blueness', bitmap, () => bitmap.shiftBlueness(5)), console.error); \ No newline at end of file diff --git a/bitmap-autobots/lib/bitmap-transforms.js b/bitmap-autobots/lib/bitmap-transforms.js index f988a04..ccaf783 100644 --- a/bitmap-autobots/lib/bitmap-transforms.js +++ b/bitmap-autobots/lib/bitmap-transforms.js @@ -140,6 +140,37 @@ Bitmap.prototype.shiftLightness = function(percentage) { return Color.fromHSLA(c.hue, c.saturation, lightness, c.alpha); }); + bitmapTransformer.writeColors(); + return clone; +}; + +Bitmap.prototype.shiftRedness = function(magnitude) { + let clone = this.clone(); + let bitmapTransformer = new BitmapTransformer(clone); + + clone.colors = clone.colors.map(c => c.shiftRedness(magnitude)); + + bitmapTransformer.writeColors(); + return clone; +}; + + +Bitmap.prototype.shiftGreenness = function(magnitude) { + let clone = this.clone(); + let bitmapTransformer = new BitmapTransformer(clone); + + clone.colors = clone.colors.map(c => c.shiftGreenness(magnitude)); + + bitmapTransformer.writeColors(); + return clone; +}; + +Bitmap.prototype.shiftBlueness = function(magnitude) { + let clone = this.clone(); + let bitmapTransformer = new BitmapTransformer(clone); + + clone.colors = clone.colors.map(c => c.shiftBlueness(magnitude)); + bitmapTransformer.writeColors(); return clone; }; \ No newline at end of file diff --git a/bitmap-autobots/lib/color-transforms.js b/bitmap-autobots/lib/color-transforms.js index d01b25c..a90a100 100644 --- a/bitmap-autobots/lib/color-transforms.js +++ b/bitmap-autobots/lib/color-transforms.js @@ -22,4 +22,16 @@ Color.prototype.toBlackAndWhite = function() { let average = Math.round((this.red + this.green + this.blue) / 3); let result = average < 128 ? 0 : 255; return new Color(result, result, result, this.alpha); +}; + +Color.prototype.shiftRedness = function(magnitude) { + return new Color(Math.max(0, Math.min(255, this.red * magnitude)), this.green, this.blue, this.alpha); +}; + +Color.prototype.shiftGreenness = function(magnitude) { + return new Color(this.red, Math.max(0, Math.min(255, this.green * magnitude)), this.blue, this.alpha); +}; + +Color.prototype.shiftBlueness = function(magnitude) { + return new Color(this.red, this.green, Math.max(0, Math.min(255, this.blue * magnitude)), this.alpha); }; \ No newline at end of file From db444babc44d33eaaecc72b14ed7f843afa74e16 Mon Sep 17 00:00:00 2001 From: Nathan Harrenstein Date: Sun, 23 Jul 2017 19:44:16 -0700 Subject: [PATCH 20/22] Update README.md --- bitmap-autobots/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bitmap-autobots/README.md b/bitmap-autobots/README.md index 894d053..e47bf22 100644 --- a/bitmap-autobots/README.md +++ b/bitmap-autobots/README.md @@ -2,11 +2,11 @@ This project contains five modules which facilitate the transformation of bitmap files: -**Color**: Handles color calcuations, parsing, and stringifying. -**Color Transforms**: Attaches various color transformations to the Color prototype. -**Bitmap**: Handles parsing of bitmap files. -**Bitmap Transformer**: Exposes methods for manipulating bitmaps. -**Bitmap Transforms**: Attaches various bitmap transformations to the Bitmap prototype. +* **Color**: Handles color calcuations, parsing, and stringifying. +* **Color Transforms**: Attaches various color transformations to the Color prototype. +* **Bitmap**: Handles parsing of bitmap files. +* **Bitmap Transformer**: Exposes methods for manipulating bitmaps. +* **Bitmap Transforms**: Attaches various bitmap transformations to the Bitmap prototype. The available transformations are: * Black and White @@ -22,4 +22,4 @@ The available transformations are: * Saturation Shift * Redness Shift * Greenness Shift -* Blueness Shift \ No newline at end of file +* Blueness Shift From 116b47db7e7cb79325d64c7f90c12942faae491c Mon Sep 17 00:00:00 2001 From: Nathan Harrenstein Date: Sun, 23 Jul 2017 22:58:07 -0700 Subject: [PATCH 21/22] Adds cli. --- bitmap-autobots/README.md | 26 ++++++- bitmap-autobots/index.js | 77 ++++++++++++++++++- bitmap-autobots/lib/bitmap-transformer.js | 14 +++- bitmap-autobots/lib/bitmap-transforms.js | 36 ++------- bitmap-autobots/lib/color-transforms.js | 29 ++++++- bitmap-autobots/test/color-transforms-test.js | 4 +- 6 files changed, 148 insertions(+), 38 deletions(-) diff --git a/bitmap-autobots/README.md b/bitmap-autobots/README.md index 894d053..a8416f5 100644 --- a/bitmap-autobots/README.md +++ b/bitmap-autobots/README.md @@ -22,4 +22,28 @@ The available transformations are: * Saturation Shift * Redness Shift * Greenness Shift -* Blueness Shift \ No newline at end of file +* Blueness Shift + +## CLI + +To use the CLI, type `node index.js [filename] [transformation[:parameter]] [transformation[:parameter]] [transformation[:parameter]]` etc. + +The transformation arguments are: +* `bw` -- Black and White +* `gray` -- Grayscale +* `sepia` -- Sepia +* `hflip` -- Horizontal Flip +* `vflip` -- Vertical Flip +* `invert` -- Color Inversion +* `rotc` -- Clockwise Rotation +* `rotcc` -- Counterclockwise Rotation +* `hshift:degree` -- Hue Shift +* `lshift:percentage` -- Lightness Shift +* `sshift:percentage` -- Saturation Shift +* `rshift:magnitude` -- Redness Shift +* `gshift:magnitude` -- Greenness Shift +* `bshift:magnitude` -- Blueness Shift + +The output will be located at `./output/custom.bmp`. + +Running the cli without arguments will produce a sample of all transformations. \ No newline at end of file diff --git a/bitmap-autobots/index.js b/bitmap-autobots/index.js index 51ec9d4..2cc0b5e 100644 --- a/bitmap-autobots/index.js +++ b/bitmap-autobots/index.js @@ -26,9 +26,9 @@ let createOutputDirectory = function() { }); }; -let loadBitmap = function() { +let loadBitmap = function(filename) { return new Promise(function(resolve, reject) { - fs.readFile(`${__dirname}/assets/palette-bitmap.bmp`, function(err, buffer) { + fs.readFile(filename, function(err, buffer) { if (err) reject(err); resolve(new Bitmap(buffer)); }); @@ -46,13 +46,82 @@ let createOutput = function(fileName, bitmap, func) { }); }; +let filePath = process.argv[2]; +let transforms = process.argv.slice(3); + +if (filePath && transforms) { + checkOutputDirectoryExists() + .catch(createOutputDirectory) + .then(() => loadBitmap(filePath)) + .then(bitmap => { + while (transforms.length > 0) { + let transform = transforms.shift(); + let transformParts = transform.split(':'); + + switch (transformParts[0]) { + case 'bw': + bitmap = bitmap.toBlackAndWhite(); + break; + case 'hflip': + bitmap = bitmap.flipHorizontally(); + break; + case 'vflip': + bitmap = bitmap.flipVertically(); + break; + case 'gray': + bitmap = bitmap.toGrayscale(); + break; + case 'invert': + bitmap = bitmap.invertColors(); + break; + case 'rotc': + bitmap = bitmap.rotateClockwise(); + break; + case 'rotcc': + bitmap = bitmap.rotateCounterclockwise(); + break; + case 'sepia': + bitmap = bitmap.toSepia(); + break; + case 'bshift': + bitmap = bitmap.shiftBlueness(Number(transformParts[1])); + break; + case 'gshift': + bitmap = bitmap.shiftGreenness(Number(transformParts[1])); + break; + case 'rshift': + bitmap = bitmap.shiftRedness(Number(transformParts[1])); + break; + case 'sshift': + bitmap = bitmap.shiftSaturation(Number(transformParts[1])); + break; + case 'hshift': + bitmap = bitmap.shiftHue(Number(transformParts[1])); + break; + case 'lshift': + bitmap = bitmap.shiftLightness(Number(transformParts[1])); + break; + default: + break; + } + } + + createOutput('custom', bitmap, () => bitmap); + + }) + .catch(err => console.error(err)); + + return; +} + + checkOutputDirectoryExists() .catch(createOutputDirectory) - .then(loadBitmap, console.error) + .then(() => loadBitmap(`${__dirname}/assets/palette-bitmap.bmp`), console.error) .then(bitmap => createOutput('black-and-white', bitmap, () => bitmap.toBlackAndWhite()), console.error) .then(bitmap => createOutput('grayscale', bitmap, () => bitmap.toGrayscale()), console.error) .then(bitmap => createOutput('sepia', bitmap, () => bitmap.toSepia()), console.error) - .then(bitmap => createOutput('inverted-colors', bitmap, () => bitmap.toInverse()), console.error) + .then(bitmap => createOutput('inverted-colors', bitmap, () => bitmap.invertColors()), console.error) .then(bitmap => createOutput('flipped-horizontally', bitmap, () => bitmap.flipHorizontally()), console.error) .then(bitmap => createOutput('flipped-vertically', bitmap, () => bitmap.flipVertically()), console.error) .then(bitmap => createOutput('rotate-clockwise', bitmap, () => bitmap.rotateClockwise()), console.error) diff --git a/bitmap-autobots/lib/bitmap-transformer.js b/bitmap-autobots/lib/bitmap-transformer.js index b34c6a0..5f4681a 100644 --- a/bitmap-autobots/lib/bitmap-transformer.js +++ b/bitmap-autobots/lib/bitmap-transformer.js @@ -43,7 +43,19 @@ BitmapTransformer.prototype.writePixels = function() { BitmapTransformer.prototype.writePixel = function(x, y, color) { let pixelOffset = this.bitmap.pixelArrayOffset + (this.bitmap.height - 1 - y) * this.bitmap.pixelRowSize + x * this.bitmap.bytesPerPixel; - let colorIndex = this.bitmap.colors.indexOf(color); + + let colorIndex = -1; + + for (var i = 0; i < this.bitmap.colors.length; i++) { + let matchesRed = this.bitmap.colors[i].red === color.red; + let matchesGreen = this.bitmap.colors[i].green === color.green; + let matchesBlue = this.bitmap.colors[i].blue === color.blue; + + if (matchesRed && matchesGreen && matchesBlue) { + colorIndex = i; + break; + } + } if (colorIndex === -1) { colorIndex = this.addColor(color); diff --git a/bitmap-autobots/lib/bitmap-transforms.js b/bitmap-autobots/lib/bitmap-transforms.js index ccaf783..01b3bf8 100644 --- a/bitmap-autobots/lib/bitmap-transforms.js +++ b/bitmap-autobots/lib/bitmap-transforms.js @@ -1,15 +1,14 @@ 'use strict'; -const Color = require('./color.js'); require('./color-transforms.js'); const Bitmap = require('./bitmap.js'); const BitmapTransformer = require('./bitmap-transformer.js'); -Bitmap.prototype.toInverse = function() { +Bitmap.prototype.invertColors = function() { let clone = this.clone(); let bitmapTransformer = new BitmapTransformer(clone); - clone.colors = clone.colors.map(c => c.toInverse()); + clone.colors = clone.colors.map(c => c.invertColors()); bitmapTransformer.writeColors(); return clone; @@ -73,7 +72,7 @@ Bitmap.prototype.rotateClockwise = function() { for (var y = 0; y < clone.height; y++) { for (var x = 0; x < clone.width; x++) { - bitmapTransformer.writePixel(this.height - 1 - y, x, this.pixelArray[y][x]); + bitmapTransformer.writePixel(clone.height - 1 - y, x, clone.pixelArray[y][x]); } } @@ -86,7 +85,7 @@ Bitmap.prototype.rotateCounterclockwise = function() { for (var y = 0; y < clone.height; y++) { for (var x = 0; x < clone.width; x++) { - bitmapTransformer.writePixel(y, this.width - 1 - x, this.pixelArray[y][x]); + bitmapTransformer.writePixel(y, clone.width - 1 - x, clone.pixelArray[y][x]); } } @@ -97,20 +96,7 @@ Bitmap.prototype.shiftHue = function(degrees) { let clone = this.clone(); let bitmapTransformer = new BitmapTransformer(clone); - clone.colors = clone.colors.map(c => { - c.getHSL(); - let hue = c.hue + degrees; - - if (Math.abs(hue) > 360) { - hue *= (1 / (hue / 360)); - } - - if (hue < 0) { - hue += 360; - } - - return Color.fromHSLA(hue, c.saturation, c.lightness, c.alpha); - }); + clone.colors = clone.colors.map(c => c.shiftHue(degrees)); bitmapTransformer.writeColors(); return clone; @@ -120,11 +106,7 @@ Bitmap.prototype.shiftSaturation = function(percentage) { let clone = this.clone(); let bitmapTransformer = new BitmapTransformer(clone); - clone.colors = clone.colors.map(c => { - c.getHSL(); - let saturation = Math.min(1, Math.max(0, c.saturation + percentage / 100)); - return Color.fromHSLA(c.hue, saturation, c.lightness, c.alpha); - }); + clone.colors = clone.colors.map(c => c.shiftSaturation(percentage)); bitmapTransformer.writeColors(); return clone; @@ -134,11 +116,7 @@ Bitmap.prototype.shiftLightness = function(percentage) { let clone = this.clone(); let bitmapTransformer = new BitmapTransformer(clone); - clone.colors = clone.colors.map(c => { - c.getHSL(); - let lightness = Math.min(1, Math.max(0, c.lightness + percentage / 100)); - return Color.fromHSLA(c.hue, c.saturation, lightness, c.alpha); - }); + clone.colors = clone.colors.map(c => c.shiftLightness(percentage)); bitmapTransformer.writeColors(); return clone; diff --git a/bitmap-autobots/lib/color-transforms.js b/bitmap-autobots/lib/color-transforms.js index a90a100..a7d6a72 100644 --- a/bitmap-autobots/lib/color-transforms.js +++ b/bitmap-autobots/lib/color-transforms.js @@ -2,7 +2,7 @@ const Color = require('./color'); -Color.prototype.toInverse = function() { +Color.prototype.invertColors = function() { return new Color(255 - this.red, 255 - this.green, 255 - this.blue, this.alpha); }; @@ -34,4 +34,31 @@ Color.prototype.shiftGreenness = function(magnitude) { Color.prototype.shiftBlueness = function(magnitude) { return new Color(this.red, this.green, Math.max(0, Math.min(255, this.blue * magnitude)), this.alpha); +}; + +Color.prototype.shiftHue = function(degrees) { + this.getHSL(); + let hue = this.hue + degrees; + + if (Math.abs(hue) > 360) { + hue *= (1 / (hue / 360)); + } + + if (hue < 0) { + hue += 360; + } + + return Color.fromHSLA(hue, this.saturation, this.lightness, this.alpha); +}; + +Color.prototype.shiftSaturation = function(percentage) { + this.getHSL(); + let saturation = Math.min(1, Math.max(0, this.saturation + percentage / 100)); + return Color.fromHSLA(this.hue, saturation, this.lightness, this.alpha); +}; + +Color.prototype.shiftLightness = function(percentage) { + this.getHSL(); + let lightness = Math.min(1, Math.max(0, this.lightness + percentage / 100)); + return Color.fromHSLA(this.hue, this.saturation, lightness, this.alpha); }; \ No newline at end of file diff --git a/bitmap-autobots/test/color-transforms-test.js b/bitmap-autobots/test/color-transforms-test.js index 0239ea1..3091452 100644 --- a/bitmap-autobots/test/color-transforms-test.js +++ b/bitmap-autobots/test/color-transforms-test.js @@ -16,9 +16,9 @@ describe('Color', function() { }); }); - describe('#toInverse()', () => { + describe('#invertColors()', () => { it('should return the correct property values.', () => { - let inverse = color.toInverse(); + let inverse = color.invertColors(); expect(inverse.red).to.equal(245); expect(inverse.green).to.equal(235); expect(inverse.blue).to.equal(225); From f7d1b5dde260e12c7dceacd76d8d4bd8cad5b222 Mon Sep 17 00:00:00 2001 From: Nathan Harrenstein Date: Mon, 24 Jul 2017 23:23:57 -0700 Subject: [PATCH 22/22] Makes reading bitmaps endian agnostic. --- bitmap-autobots/lib/bitmap.js | 93 ++++++++++++------------ bitmap-autobots/lib/buffer-extensions.js | 49 +++++++++++++ 2 files changed, 96 insertions(+), 46 deletions(-) create mode 100644 bitmap-autobots/lib/buffer-extensions.js diff --git a/bitmap-autobots/lib/bitmap.js b/bitmap-autobots/lib/bitmap.js index 6419a71..86e2a1a 100644 --- a/bitmap-autobots/lib/bitmap.js +++ b/bitmap-autobots/lib/bitmap.js @@ -1,5 +1,6 @@ 'use strict'; +require('./buffer-extensions.js'); const Color = require('./color.js'); module.exports = Bitmap; @@ -21,85 +22,85 @@ Bitmap.prototype.clone = function() { function readHeader() { this.type = this.buffer.toString('utf-8', 0, 2); - this.size = this.buffer.readInt32LE(2); - this.reserved1 = this.buffer.readInt32LE(6); - this.reserved2 = this.buffer.readInt32LE(8); - this.pixelArrayOffset = this.buffer.readInt32LE(10); + this.size = this.buffer.readInt32(2); + this.reserved1 = this.buffer.readInt32(6); + this.reserved2 = this.buffer.readInt32(8); + this.pixelArrayOffset = this.buffer.readInt32(10); } function readBitmapHeader() { - this.bitmapHeaderSize = this.buffer.readUInt32LE(14); + this.bitmapHeaderSize = this.buffer.readUInt32(14); if (this.bitmapHeaderSize === 12) { readBitmapCoreHeader.call(this); return; } - this.colorPlanes = this.buffer.readUInt16LE(26); - this.bitsPerPixel = this.buffer.readUInt16LE(28); - this.compression = this.buffer.readUInt32LE(30); - this.size = this.buffer.readUInt32LE(34); - this.colorCount = this.buffer.readUInt32LE(46); - this.importantColorCount = this.buffer.readUInt32LE(50); + this.colorPlanes = this.buffer.readUInt16(26); + this.bitsPerPixel = this.buffer.readUInt16(28); + this.compression = this.buffer.readUInt32(30); + this.size = this.buffer.readUInt32(34); + this.colorCount = this.buffer.readUInt32(46); + this.importantColorCount = this.buffer.readUInt32(50); if (this.bitmapHeaderSize === 16 || this.bitmapHeaderSize === 64) { readOS22BitmapHeader.call(this); return; } - this.width = this.buffer.readInt32LE(18); - this.height = this.buffer.readInt32LE(22); - this.horizontalResolution = this.buffer.readInt32LE(38); - this.verticalResolution = this.buffer.readInt32LE(42); + this.width = this.buffer.readInt32(18); + this.height = this.buffer.readInt32(22); + this.horizontalResolution = this.buffer.readInt32(38); + this.verticalResolution = this.buffer.readInt32(42); if (this.bitmapHeaderSize === 40) { return; } - this.redMask = this.buffer.readUInt32LE(54); - this.greenMask = this.buffer.readUInt32LE(58); - this.blueMask = this.buffer.readUInt32LE(62); - this.alphaMask = this.buffer.readUInt32LE(66); - this.colorSpaceType = this.buffer.readUInt32LE(70); + this.redMask = this.buffer.readUInt32(54); + this.greenMask = this.buffer.readUInt32(58); + this.blueMask = this.buffer.readUInt32(62); + this.alphaMask = this.buffer.readUInt32(66); + this.colorSpaceType = this.buffer.readUInt32(70); this.endpoints = getCIEXYZTriple(this.buffer.slice(74, 110)); - this.gammaRed = this.buffer.readUInt32LE(110); - this.gammaGreen = this.buffer.readUInt32LE(114); - this.gammaBlue = this.buffer.readUInt32LE(118); - this.intent = this.buffer.readUInt32LE(122); - this.profileData = this.buffer.readUInt32LE(126); - this.profileSize = this.buffer.readUInt32LE(130); - this.reserved = this.buffer.readUInt32LE(134); + this.gammaRed = this.buffer.readUInt32(110); + this.gammaGreen = this.buffer.readUInt32(114); + this.gammaBlue = this.buffer.readUInt32(118); + this.intent = this.buffer.readUInt32(122); + this.profileData = this.buffer.readUInt32(126); + this.profileSize = this.buffer.readUInt32(130); + this.reserved = this.buffer.readUInt32(134); } function readBitmapCoreHeader() { - this.width = this.buffer.readUInt16LE(18); - this.height = this.buffer.readUInt16LE(20); - this.colorPlanes = this.buffer.readUInt16LE(22); - this.bitsPerPixel = this.buffer.readUInt16LE(24); + this.width = this.buffer.readUInt16(18); + this.height = this.buffer.readUInt16(20); + this.colorPlanes = this.buffer.readUInt16(22); + this.bitsPerPixel = this.buffer.readUInt16(24); } function readOS22BitmapHeader() { - this.width = this.buffer.readUInt32LE(18); - this.height = this.buffer.readUInt32LE(22); - this.horizontalResolution = this.buffer.readUInt32LE(38); - this.verticalResolution = this.buffer.readUInt32LE(42); + this.width = this.buffer.readUInt32(18); + this.height = this.buffer.readUInt32(22); + this.horizontalResolution = this.buffer.readUInt32(38); + this.verticalResolution = this.buffer.readUInt32(42); let short = this.bitmapHeaderSize == 16; - this.resolutionUnit = short ? 0 : this.buffer.readUInt16LE(54); - this.reserved = short ? 0 : this.buffer.readUInt16LE(56); - this.orientation = short ? 0 : this.buffer.readUInt16LE(58); - this.halftoning = short ? 0 : this.buffer.readUInt16LE(60); - this.halftoneSize1 = short ? 0 : this.buffer.readUInt32LE(62); - this.halftoneSize2 = short ? 0 : this.buffer.readUInt32LE(66); - this.colorSpace = short ? 0 : this.buffer.readUInt32LE(70); - this.appData = short ? 0 : this.buffer.readUInt32LE(74); + this.resolutionUnit = short ? 0 : this.buffer.readUInt16(54); + this.reserved = short ? 0 : this.buffer.readUInt16(56); + this.orientation = short ? 0 : this.buffer.readUInt16(58); + this.halftoning = short ? 0 : this.buffer.readUInt16(60); + this.halftoneSize1 = short ? 0 : this.buffer.readUInt32(62); + this.halftoneSize2 = short ? 0 : this.buffer.readUInt32(66); + this.colorSpace = short ? 0 : this.buffer.readUInt32(70); + this.appData = short ? 0 : this.buffer.readUInt32(74); } function getCIEXYZTriple(buffer) { - let red = new CIEXYZ(buffer.readFloatLE(0), buffer.readFloatLE(4), buffer.readFloatLE(8)); - let green = new CIEXYZ(buffer.readFloatLE(12), buffer.readFloatLE(16), buffer.readFloatLE(20)); - let blue = new CIEXYZ(buffer.readFloatLE(24), buffer.readFloatLE(28), buffer.readFloatLE(32)); + let red = new CIEXYZ(buffer.readFloat(0), buffer.readFloat(4), buffer.readFloat(8)); + let green = new CIEXYZ(buffer.readFloat(12), buffer.readFloat(16), buffer.readFloat(20)); + let blue = new CIEXYZ(buffer.readFloat(24), buffer.readFloat(28), buffer.readFloat(32)); return new CIEXYZTriple(red, green, blue); } diff --git a/bitmap-autobots/lib/buffer-extensions.js b/bitmap-autobots/lib/buffer-extensions.js new file mode 100644 index 0000000..5eccd9b --- /dev/null +++ b/bitmap-autobots/lib/buffer-extensions.js @@ -0,0 +1,49 @@ +'use strict'; + +const os = require('os'); + +let endianness = os.endianness(); + +Buffer.prototype.read = function(offset, valueType) { + if (endianness === 'LE') { + switch (valueType) { + case 'int32': + return this.readInt32LE(offset); + case 'uint16': + return this.readUInt16LE(offset); + case 'uint32': + return this.readInt32LE(offset); + case 'float': + return this.readFloatLE(offset); + } + } else { + switch (valueType) { + case 'int32': + return this.readInt32BE(offset); + case 'uint16': + return this.readUInt16BE(offset); + case 'uint32': + return this.readInt32BE(offset); + case 'float': + return this.readFloatBE(offset); + } + } + + return null; +}; + +Buffer.prototype.readInt32 = function(offset) { + return this.read(offset, 'int32'); +}; + +Buffer.prototype.readUInt16 = function(offset) { + return this.read(offset, 'uint16'); +}; + +Buffer.prototype.readUInt32 = function(offset) { + return this.read(offset, 'uint32'); +}; + +Buffer.prototype.readFloat = function(offset) { + return this.read(offset, 'float'); +}; \ No newline at end of file